Wymagania:
- Jak poprzednio + przyciski typu microswitch
====== Wejście ======
Poprzednio opisałem sposób na "wysyłanie informacji" z uC jednak za pomocą portów We/Wy możemy również informację odczytywać. Przeanalizujmy poniższy przykład:
===== Hardware =====
{{:pl:avrc:art:schematic_buttons1.jpg}}Wartość R3 i R4 to 10 [kΩ]
===== Software =====
/*
* main.c
*
* Created on: 5-03-2011
* Author: Maciek
* Przyciski 1
*/
#include
#include
//Knfiguracja sprzętowa: do portu B podpięte są 2 diody do pinów 0 i 1
//Do pinów 2 i 3 podpięte są 2 przyciski podciągnięte (pull-up) do Vcc przez rezystory 10 [kOhm]
#define LED_DIR DDRB
#define LED_P PORTB
#define LEDA 0
#define LEDB 1
#define SW_DIR DDRB
//Rejestr zapisu (pull-up)
#define SW_W PORTB
//Rejestr odczytu
#define SW_R PINB
#define SWA 2
#define SWB 3
//Uwaga:
//Konfiguracja sprzętowa powoduje że stanem aktywnym dla przycisku jest 0!
//Podciągnięcie do Vcc, przycisk zwiera z Gnd
int main()
{
//Inicjalizacja portów
//2 pierwsze piny jako wyjcia
LED_DIR |= ( (1<
===== Modyfikacja =====
Powyższą konfigurację możemy uprościć. Rezystory podciągające przyciski do Vcc możemy zastąpić wewnętrznym podciągnięciem, tym samym które umożliwia ustalenie stanu wysokiego na pinach. Z powyższego schematu usuwamy rezystory R3 oraz R4. Zgodnie z notą katalogową((Sekcja 25.2 __DC Characteristics__: wielkość Rpu)) wartość wewnętrzego rezystora podciągającego zawiera się w zakresie 30÷80 [kΩ].
Zmodyfikujmy program:
//SEKCJA A
//Przyciski jako wejscia
SW_DIR&=~((1<Działanie układu nie zmieni się, a pozbyliśmy się 2 elementów.
====== Dzień bez całki dniem straconym ======
Niestety pomimo że poruszane do tej pory zagadnienia są bardzo proste (bo logika 0 1 jest prosta a konkretniej: najprostsza możliwa). W tym momencie należy zwrócić uwagę na trochę bardziej zawiłe wiadomości których nie można pominąć. Przeanalizujmy działanie poniższego programu (Taka sama konfiguracja sprzętowa jak powyżej): /*
* main.c
*
* Created on: 5-03-2011
* Author: Maciek
* Przyciski 3
*/
#include
#include
#define LED_DIR DDRB
#define LED_P PORTB
#define LEDA 0
#define LEDB 1
#define SW_DIR DDRB
#define SW_W PORTB
#define SW_R PINB
#define SWA 2
#define SWB 3
int main()
{
//Problem jest lepiej widoczny gdy zapala się tylko 1 dioda
//Więc pin LEDB nie zostaje ustawiony jako wyjście
LED_DIR |= ( (1< Powyższy program powinien przełączać diodę (gasić gdy zapalona i vice versa), jednak jak się okazuje nie zawsze się tak dzieje. Szczególnie gdy przycisk jest naciskany gwałtownie. Dioda zamiast zmienić swój stan, pozostaje w tym samym lub krótko błyska.
===== Napięcia a stany logiczne =====
Przed przejściem do setna problemu przyjrzyjmy się poniższemu wykresowi (pochodzi ze strony [[http://www.interfacebus.com/voltage_threshold.html]]):\\
{{:pl:avrc:art:chart_ic_voltage_switching_levels.png }}\\
* VCC - Napięcie zasilania (w tym przypadku 5V, przy innych napięciach poziomy się zmieniają)
* VIH (Input High) - Najniższe napięcie które zostanie rozpoznane jako logiczna 1
* VIL (Input Low) - Najwyższe napięcie które zostanie rozpoznane jako logiczne 0
* VOL (Output Low) - Najwyższe napięcie jakie zostanie ustawione przez urządzenie gdy wyjście znajduje się w stanie logicznego 0
* VOH (Output High) - Najwyższe napięcie jakie zostanie ustawione przez urządzenie gdy wyjście znajduje się w stanie logicznej 1
* VT (Threshold) - Napięcie progowe przy którym następuje zmiana stanów logicznych (tylko w przypadku niektórych układów, większość pracuje przy zakresach jak wyżej)
Wykres przedstawia różne rodziny układów cyfrowych i poziomy napięć przy jakich następują zmiany.\\
W rozdziale 25.2 __DC Characteristics__ noty katalogowej układu ATmega8A opisane są następujące poziomy napięć (przy napięciu zasilania 5 [V]):
^Symbol ^Wartość min [V] ^ Wartość max [V] ^
| VIL | -0.5 | 0.2 VCC |
| VIH | 0.6 VCC | VCC + 0.5 |
| VOL | - | 0.9 |
| VOH | 4.2 | - |
Uwaga: W nocie znajduje się również informacja o poziomach przy zasilaniu z 3 [V]. Podana tabela jest uproszczeniem dla większości pinów, jednak producent podaje inne wielkości dla niektórych specjalnych pinów (np. reset ma inne wartości, reset pracujący jako port I/O jeszcze inne itd.)\\
Wnioski:
* Stanu logicznemu "1" nie odpowiada jedno napięcie 5V ale cały zakres, tak samo stan logiczny "0" to nie jest potencjał masy.
* Co więcej istnieje zakres napięć dla którego nie możemy przewidzieć jak zostanie on zinterpretowany!
* Różne rodziny układów charakteryzują się różnymi poziomami napięć.
===== Drgania styków =====
Problem jaki występuje tutaj to drgania styków przełącznika. Zamiast jednego naciśnięcia uC odczytuje kilka! Problem jest stosunkowo dobrze opisany np. na tej stronie: [[http://www.ganssle.com/debouncing.htm|Debouncing]]. Zwróćmy uwagę na oscylogram (Pochodzi z wyżej wymienionej strony www):\\
{{:pl:avrc:art:debounce_switcha.jpg }}\\
Górny wykres przedstawia napięcie a wykres dolny interpretację logiczną tego napięcia. Dobrze widać że zamiast skoku jednostkowego otrzymaliśmy oscylacje. Sytuacja taka jest bardzo niepożądana (przede wszystkim frustrująca dla użytkownika który nie może uzyskać dokładnego cyfrowego nastawu!). Zwróćmy uwagę na fakt że rozbieżność w parametrach przełączników jest dosyć duża, jednak większość przełączników zmieściła się w zakresie czasu oscylacji 10 [ms] (patrz strona [[http://www.ganssle.com/debouncing.htm|Debouncing]]). Warto też zwrócić uwagę na fakt że microswitche mają dosyć dobre parametry jeżeli chodzi o czas oscylacji, im większy przełącznik tym te parametry stają się coraz gorsze. Istnieją 2 proste sposoby poradzenia sobie z tą sytuacją:
==== Sprzętowy ====
Pomiędzy nóżki swicha wstawiamy kondensator tworząc //układ całkujący// (układ inercyjny RC pierwszego rzędu).\\
{{:pl:avrc:art:low_pass_filter.gif|}}\\
Skoro z oscylogramu wynika że drgania trwają 8 [ms] załóżmy (zgodnie z zasadą: lepiej raz a dobrze) że mogą trwać 20 [ms] (jest to czas zarazem wystarczający jak i nieobciążający użytkownika koniecznością zbyt długiego przytrzymywania przycisku). Wartość pojemności dobieramy zgodnie z wzorem na stałą czasową obwodu RC (τ=RC -> C=τ/R). Podczas prób wybrałem (bez jakichkolwiek obliczeń) kondensator 100 [nF] (z zestawu), całkowicie spełnił swoją rolę. Zauważmy że stała czasowa dla takiego układu (załóżmy że R podciągający ma wartość 40 [kΩ]) to tylko 4 [ms] (tak jak napisałem wyżej microswitche mają dobre parametry i taka stała czasowa wystarcza w tym prostym przypadku), jeżeli faktycznie drgania są długie (i przede wszystkim mamy dostęp do różnych kondensatorów) musimy zastosować odpowiednio większy kondensator (dla 20 [ms] przy 40 [kΩ] rezystorze będzie to około 1 [μF]).
==== Programowy ====
Rozwiązanie programowe jest również proste i ma tą zaletę że nie wymaga dodawania żadnych elementów do układu (mniej elementów -> mniejsza cena, wielkość itd.). Algorytm jest bardzo prosty: //wykryłem zmianę -> czekam pewien czas -> jeżeli zmiana dalej się utrzymuje przyjmuje że nie jest ona przypadkowa//. Wystarczy zmodyfikować główną pętle w podany sposób: while(1)
{
nowy_stan = ((SW_R&(1<
==== Inne ====
W większości przypadków przedstawione rozwiązania wystarczają, jednak kiedy potrzebna jest duża niezawodność stosuje się inne rozwiązania. Przede wszystkim trzeba określić jak długo trwają drgania na przełączniku (pomiary oscyloskopem). Istnieje bardzo dużo sposobów, np. zastosowanie:
* Dodatkowego bufora z [[wp>Schmitt_trigger|bramką Schmitta]]
* Specjalistycznych układów scalonych np. [[http://pdfserv.maxim-ic.com/en/an/AN287.pdf|Maxim MAX681x]]
* Przerzutników RS
====== Przerwania ======
Przerwanie jest to specjalny podprogram który zostaje uruchomiony przez pewien sygnał. W wyniku pojawienia się takiego sygnału aktualnie wykonywany program zostaje wstrzymany i układ zaczyna wykonywać odpowiedni podprogram. Przerwania są na sztywno ustawione w pamięci układu: na samym początku pamięci programu znajduje się tablica zawierająca adresy wszystkich przerwań (nawet tych które nie istnieją, wtedy prowadzą one do początku programu (reset) ale taka sytuacja występuje tylko w przypadku błędu, przerwania których nie zdefiniowaliśmy nie powinny być wywoływane). W środowisku WinAVR obsługa przerwań jest bardzo prosta (kompilator sam dba o to żeby np. przerwanie nie zakłóciło wykonywania głównego programu, a tablica wektorów przerwań jest tworzona automatycznie). Aby obsługiwać przerwania należy skorzystać z nagłówka [[http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html|]]. Definiowanie przerwania jest bardzo proste: korzystamy z makra ISR (Interrupt Service Routine):
#include
ISR(NAZWA_przerwania)
{
// kod
}
Po resecie wszystkie przerywania są zablokowane, do włączania i wyłączania wszystkich przerwań (globalnie) stosujemy makra:
cli(); //(clear interrupts) Wyłącza przerwania
sei(); //(set interrupts) Włącza przerwania
W układzie ATmega8A jest dostępnych 19 przerwań((Nota katalogowa dział 11 __Interrupts__))
===== Przerwania zewnętrzne =====
Są wyzwalane przez piny INT0 INT1 (porty PD2 PD3). Oba piny ustawione jako źródło przerwań mogą reagować na 1 z 4 sposobów (Nota tabela 13-1) w zależności od ustawień w rejestrze MCUCR (MicroController Unit Control Register):
^ MCUCR - MCU Control Register ^^^^^^^^^
^Bit | 7| 6| 5| 4| 3| 2| 1| 0|
^Nazwa bitu | /SE/ | /SM2/ | /SM1/ | /SM0/ | ISC11 | ISC10 | ISC01 | ISC00 |
^Znaczenie | | | | | A | B | A | B |
^ ::: | W tym przypadku nie istotne |||| INT1 || INT0 ||
^A ^B ^Opis ^
|0 |0 |Przerwanie jest generowane przez logiczne 0|
|0 |1 |Jakakolwiek zmiana stanu logicznego generuje przerwanie|
|1 |0 |Przerwanie jest generowane przez opadające zbocze|
|1 |1 |Przerwanie jest generowane przez rosnące zbocze|
Ex: MCUCR |= (1<
Aby przerwanie działało nie tylko wystarczy odblokować przerwanie globalnie (''sei();'') musimy też zdefiniować które konkretnie przerwania są aktywne, w tym przypadku będzie do tego służył rejestr GICR (General Interrupt Control Register) i jego bity: INT0 oraz INT1;
^ GICR - General Interrupt Control ^^^^^^^^^
^Bit | 7| 6| 5| 4| 3| 2| 1| 0|
^Nazwa bitu | INT1 | INT0 | -xx- | -xx- | -xx- | -xx- | /IVSEL/ | /IVCE/ |
GICR |= (1<
==== Przykład 1 ====
=== Hardware ===
{{:pl:avrc:art:schematic_interrupts1.png|}}
=== Software ===
/*
* main.c
*
* Created on: 20-03-2011
* Author: Maciek
* Przerwania 1
*/
#include
#include
#include
//Knfiguracja sprzętowa: do portu B podpięte są 2 diody do pinów 0 i 1 katody do masy
//Do pinów INT0 i INT1 portu D są podpięte 2 przyciski
#define LED_DIR DDRB
#define LED_P PORTB
#define LEDA 0
#define LEDB 1
#define SW_DIR DDRD
#define SW_W PORTD
#define SW_R PIND
//Zmienna globalna
//Zauważmy że tym razem zużycie pamięci danych nie jest 0 lecz wynosi 1 bajt!
//slowo kluczowe volatile oznacza ulotny. Kompilator musi sie liczyc z tym ze ta zmienna
//bedzie sie zmieniac w trakcie pracy programu!
volatile char LED=2;
//0
ISR (SIG_INTERRUPT0)
{
//Zapalamy diode B kiedy przerywanie jest wykonywane
LED_P |= (1<
=== Wnioski ===
Co robi nasz program? Jeden przycisk zapala diodę drugi ją gasi. Co dzieje się z drugą diodą? Świeci się cały czas kiedy trzymamy przycisk! Co to oznacza? W pętli głównej gasimy diodę B, więc jeżeli pali ona się cały czas oznacza to że przerwanie jest wyzwalane cały czas. Dzieje się tak ponieważ nie ustawiliśmy rejestru MCUCR, zgodnie z [[|tabelką]] przerwanie jest generowane przez stan logiczny niski, czyli cały czas podczas przyciskania przycisku. Zmodyfikujmy program w następujący sposób:
//Ustawiamy przerwania
//UWAGA: Kiedys rejestr nazywał się GIMSK, nazwe zmieniono!
//INT0 wyzwalany rosnącym zboczem
//INT1 zboczem opadającym
MCUCR |= (1< Co się zmieniło? Dioda zapala się w momencie kiedy puszczamy przycisk zapalający (zbocze rosnące) i gaśnie dokładnie w momencie naciśnięcia drugiego przycisku (zbocze opadające). Wszystko zgodnie z oczekiwaniami.
==== Przykład 2 ====
Hardware tak jak w poprzednim przykładzie. Tym razem zadaniem jest sprawdzenie i zrozumienie działania poniższego programu.
/*
* main.c
*
* Created on: 20-03-2011
* Author: Maciek
* Przerwania 02
*/
#include
#include
//Knfiguracja sprzętowa: do portu B podpięte są 2 diody do pinów 0 i 1 katody do masy
//Do pinów INT0 i INT1 portu D są podpięte 2 przyciski
#define LED_DIR DDRB
#define LED_P PORTB
#define LEDA 0
#define LEDB 1
#define SW_DIR DDRD
#define SW_W PORTD
#define SW_R PIND
volatile char period=0;
//Przerwania musza byc jak najkrotsze!
ISR (SIG_INTERRUPT0)
{
//Zwiekszaj tylko do 255
if(period<250)period+=5;
}
//Jak już jest warto wykozystac do czegos
ISR (SIG_INTERRUPT1)
{
period = 0;
}
int main()
{
uint8_t i=0;
uint8_t delay=0;
//Port przycisków jako wejcie
SW_DIR = 0;
//Podciagamy zeby nie stosowac dodatkowych rezystorow
SW_W = 0xFF;
//Port LED, jako wyjscie i na poczatku ustalamy ze diody sie nie swieca
LED_DIR|=(1<i) LED_P |= (1<0)--period;
}
}
}
===== Lista wszystkich przerwań układu ATMega8A =====
Niższa liczba porządkowa oznacza wyższy priorytet.
^ Lp. ^ Nazwa ^ Źródło/Opis ^
| 1 | (RESET) | Pin, Brownout, Watchdog, Power-on Reset |
| 2 | (INT0) SIG_INTERRUPT0 | Zewnętrzny pin |
| 3 | (INT1) SIG_INTERRUPT1 | Zewnętrzny pin |
| 4 | SIG_OUTPUT_COMPARE2 | Timer/Counter2: wartość licznika osiągnęła zadaną wartość |
| 5 | SIG_OVERFLOW2 | Timer/Counter2: przepełnienie licznika |
| 6 | SIG_INPUT_CAPTURE1| Timer/Counter1: przechwycenie |
| 7 | SIG_OUTPUT_COMPARE1A| Timer/Counter1: wartość licznika osiągnęła zadaną wartość A|
| 8 | SIG_OUTPUT_COMPARE1B| Timer/Counter1: wartość licznika osiągnęła zadaną wartość B|
| 9 | SIG_OVERFLOW1| Timer/Counter1: przepełnienie licznika|
| 10| SIG_OVERFLOW0| Timer/Counter0: przepełnienie licznika|
| 11| SIG_SPI| SPI,STC Transmisja szeregowa zakończona |
| 12| SIG_UART_RECV| USART Odbiór zakończony |
| 13| SIG_UART_DATA| USART Rejestr danych pusty |
| 14| SIG_UART_TRANS| USART Nadawanie zakończone |
| 15| SIG_ADC| ACD konwersja dobiegła końca |
| 16| SIG_EEPROM_READY| Pamięć EEPROM gotowa |
| 17| SIG_COMPARATOR| Komparator analogowy |
| 18| SIG_2WIRE_SERIAL| Interfejs TWI(I2C) |
| 19| SIG_SPM_READY| Pamięć FLASH gotowa (bootloader)|
====== Zadania ======
* Wiedza zdobyta w rozdziałach W02 i W03 pozwala już zbudować całą gamę urządzeń!
* Zbuduj zamek szyfrowy.
* Zbuduj grę Simon [[http://www.youtube.com/watch?v=4YhVyt4q5HI]] (na początku z sztywno ustawioną kolejnością, generowanie liczb losowych nie jest łatwe)
* Zastanów się jak za pomocą 1 portu przerwań obsłużyć kilka przycisków
====== Uwagi ======
* Eclipse z jakiegoś powodu nie zapisuje automatycznie edytowanego pliku, warto wyrobić nawyk częstego zapisywania ( Ctrl+S )
* Warto sprawdzać dostępność aktualizacji __Help->Check for Updates__