Zakres laboratorium:
- Zapoznanie z płytką EduTar
- Typy zmiennych dla mikrokontrolerów AVR
- Operacje bitowe
- Rejestry DDRx, PORTx, PINx – sterowanie portami
- Odczytywanie stanu pinu – obsługa przycisków, drgania styków
Przykładowy schemat z laboratorium wstępnego:
1. Zapoznanie z płytką EduTar
Strona płytki EduTar: link
Dane techniczne mikrokontrolera ATmega328P:
- Rodzina: AVR
- Organizacja pamięci Flash: 32kb
- Pamięć EEPROM: 1024B
- Pamięć SRAM: 2048B
- Częstotliwość maksymalna: 20MHz
- Napięcie pracy: 1,8 – 5,5V
- Liczba wejść/wyjść: 23
- Liczba kanałów PWM: 4
- Obudowa: TQFP32
Moduł Arduino nano:
- Atmega328P
- programowanie/ komunikacja z komputerem: UART
- częstotliwość taktowania przy wykorzystaniu zewnętrznego kwarcu: 16MHz
- pinout zgodny z rysunkiem: link
2. Typy zmiennych dla mikrokontrolerów AVR
Mikrokontrolery AVR np. ATmega328P są mikrokontrolerami 8-biotowymi (wszystkie rejestry mają pojemność 8 bitów). Oznacza to, że potrafią wykonywać proste operacje tylko na liczbach 8-biotowych, w celu wykonania operacji na większych liczbach mikrokontroler musi wykonać kilka instrukcji, co wpływa na wydajność programu. Dlatego lepiej jest korzystać z mniejszych zmiennych niż 32 bitowe, które zostały zdefiniowane w bibliotece (stdint.h).
Typy zmiennych dla liczb dodatnich (bez znaku) i całkowitoliczbowych:
- uint8_t – 0 – 255
- uint16_t – 0 – 65535
- uint32_t – 0 – 4294967295
- uint64_t – 0 – 18446744073709551615
Zmienne z znakiem i całkowitoliczbowe:
- int8_t – -128 – 127
- int16_t – -32768 – 32767
- int32_t – -2147483648 – 2147483647
- int64_t – -9223372036854775808 – 9223372036854775807
Zmienne dla liczb zmiennopozycyjnych (liczba z przecinkiem) – typy zajmujące dużo pamięci:
- float (4 bajty)
- double (8 bajty, w Arduino 4 bajty)
Zmienne inne:
- char – zmienna 8 bitowa do przechowywania znaków
- bool – true/false – przechowuje wartość na pierwszej pozycji bajtu (0b00000001/0b00000000) ale i tak zajmuje 8 bitów.
Zadanie 2.1
- wyniki zapisywać na kartce w celu pokazania prowadzącemu
- przygotować i skompilować poniższy program
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <avr/io.h> #include <stdlib.h> #include <string.h> #include <util/delay.h> #ifndef _BV #define _BV(bit) (1<<(bit)) #endif #ifndef sbi #define sbi(reg,bit) reg |= (_BV(bit)) #endif #ifndef cbi #define cbi(reg,bit) reg &= ~(_BV(bit)) #endif #ifndef tbi #define tbi(reg,bit) reg ^= (_BV(bit)) #endif volatile uint8_t tab[100]; int main() { int i=0; while (1) { tab[1]=i++; _delay_ms(1000); } } |
- ile pamięci zajmuje program przy rozmiarze tablicy 10?
- zmienić rozmiary tablicy na 100,1000,2000
- zanotować informacje o wykorzystanej ilości pamięci
- zmienić typ uin8_t na int i przeprowadzić takie same testy
Zadanie 2.2
- skompilować poniższy program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <avr/io.h> #include <stdlib.h> #include <string.h> #include <util/delay.h> #ifndef _BV #define _BV(bit) (1<<(bit)) #endif #ifndef sbi #define sbi(reg,bit) reg |= (_BV(bit)) #endif #ifndef cbi #define cbi(reg,bit) reg &= ~(_BV(bit)) #endif #ifndef tbi #define tbi(reg,bit) reg ^= (_BV(bit)) #endif volatile float zmienna; int main() { zmienna=5; //zmienna+=41; //a jak zmieni się rozmiar programu po wykonaniu operacji while (1) { _delay_ms(1000); } } |
- ile pamięci zajmuje program, którzy wykorzystuje liczby zmiennoprzecinkowe
- zmienić typ liczby “zmienna” z float na uint8_t
- ile pamięci teraz zajmuje program, ile pamięci dodatkowo musi wykorzystać kompilator aby móc obsługiwać liczby zmiennoprzecinkowe?
Jakie wnioski można wysunąć? Przemyśleć i zaprezentować prowadzącemu wyniki i przedstawić wnioski.
3. Operacje bitowe
- Zaprzeczenie NOT: ~
- Iloczyn AND: &
- Suma OR: |
- Suma wyłaczająca XOR: ^
- Przesunięcie w prawo: >>
- Przesunięcie w lewo: <<
1 2 3 4 5 6 7 8 9 |
#define _BV(bit) (1<<(bit)) #define sbi(reg,bit) reg |= (_BV(bit)) czyli: reg|= (1<<bit) #define cbi(reg,bit) reg &= ~(_BV(bit)) reg &= ~ (1<<bit) |
Bity, bajty itd. – o co w tym chodzi???
uint8_t liczba= 209; uint8_t liczba= 0b11010001; uint8_t liczba= 0xD1;
int8_t liczba= -47; int8_t liczba= -0b11010001; int8_t liczba= -0xD1;
uint16_t liczba= 37585; uint16_t liczba= 0b1001001011010001; uint8_t liczba= 0x92D1;
4. Rejestry DDRx, PORTx, PINx – sterowanie portami
Wyprowadzenia mikrokontrolera – sterowanie:
- Port B – PB0-PB7 – DDRB, PORTB, PINB
- Port C – PC0-PC6 – DDRC, PORTC, PINC
- Port D – PD0-PD7 – DDRD, PORTD, PIND
Rejestry – a o co chodzi?
Rejestr DDRx – rejestr kierunku:
- 0 – ustawia wyprowadzenie jako wejście
- 1 – ustawia wyprowadzenie jako wyjście
Rejestr PORTx – rejestr wyjściowy – gdy DDRx ==1:
- 0 – ustawia wyprowadzenie w stan niski
- 1 – ustawia wyprowadzenie w stan wysoki
Rejestr PORTx – rejestr wyjściowy – gdy DDRx ==0:
- 0 – nic nie robi (stan wysokiej impedancji)
- 1 – pull-UP – podciągnięcie wyprowadzenia do VCC przez rezystor 10k (zapobiega pojawianiu się nieokreślonych stanów na wyprowadzeniu)
Rejestr PINx – rejestr wejściowy – tylko do odczytu:
- 0 – oznacza stan niski na wyprowadzeniu
- 1 – oznacza stan wysoki na wyprowadzeniu
Podsumowując:
DDRx | PORTx | PINx* | |
Wyjście stan niski | 1 | 0 | |
Wyjście stan wysoki | 1 | 1 | |
Wejście wysoka impedancja | 0 | 0 | |
Wejście PULL-UP | 0 | 1 | |
Odczyt wejścia – stan wysoki | 0 | x | 1 |
Odczyt wejścia – stan niski | 0 | x | 0 |
*-tylko do odczytu
W celu ustawienia wyprowadzenie PD6 jako wyjście w stanie wysokim należy:
1 2 |
sbi(DDRD,PD6); //Ustawia wyprowadzenie PD6 do pracy jako wyjście sbi(PORTD,PD6); //Ustawia stan wysoki na wyprowadzeniu PD6 |
W celu ustawienia wyprowadzenie PD6 jako wyjście w stanie niskim należy:
1 2 |
sbi(DDRD,PD6); //Ustawia wyprowadzenie PD6 do pracy jako wyjście cbi(PORTD,PD6); //Ustawia stan niski na wyprowadzeniu PD6 |
Program realizujący miganie diodą:
1 2 3 4 5 6 7 8 9 10 11 |
int main() { sbi(DDRD, PD6); while (1) { sbi(PORTD, PD6); _delay_ms(500); cbi(PORTD, PD6); _delay_ms(500); } } |
Można też krócej:
1 2 3 4 5 6 7 8 |
int main() { sbi(DDRD, PD6); while (1) { tbi(PORTD, PD6); _delay_ms(500); } } |
Rejestry są zmiennymi do których można zapisywać wartość, więc są różne sposoby ich modyfikacji:
- DDRD=0xFF;
- DDRD |= (1<<PD0) | … | (1<<PD7)
- sbi(DDRD, PD0) … sbi(DDRD, PD7)
Sterowanie wyprowadzeniami mikrokontrolera jako wyjścia.
Zadanie 4.1
- podłączyć diody:
- LED1 do PD0, …………., LED8 do PD7
- włączyć wszystkie diody LED – jak można to zrobić, przetestować 3 sposoby
- napisać funkcję, która będzie mrugała wszystkimi diodami z częstotliwością 1s
Zadanie 4.2
- podłączyć diody jak w poprzednim zadaniu
- przygotować funkcje, która będzie realizowała zadanie “biegający punkt”. Jedna dioda na linijce diodowej jest włączona i porusza się w lewo lub w prawo – w zależności gdzie świecący punkt był wcześniej.
5. Odczytywanie stanu pinu – obsługa przycisków, drgania styków
Sterowanie wyprowadzeniami mikrokontrolera jako wejścia.
W pliku nagłówkowym <avr/sfr_defs.h> zdefiniowana makra “bit_is_set(reg,bit)” i “bit_is_clear(reg,bit)”
Zadanie 5.1
- podłączyć diody jak w poprzednim zadaniu
- podłączyć przewód do PB0
- stworzyć funkcję: przyciśnięcie przycisku (przewód podłączony do GND) – wszystkie diody świecą się. Przycisk puszczony (przewód odłączone od GND) – wszystkie diody są wyłączone
- jak działa program? czy jest jakiś problem? jak go rozwiązać?
Zadanie 5.2
- podłączyć diody jak w poprzednim zadaniu
- podłączyć przewód do PB1
- napisać program przycisku bistabilnego – przyciśnięcie przycisku (przewód podłączony do GND i odłączony) – diody świecą. Drugi raz przyciśnięcie przycisku (przewód podłączony do GND i odłączony) – wszystkie diody są wyłączone
- jak działa program? czy jest jakiś problem? jak go rozwiązać?
Zadanie 5.3
- podłączyć diody jak w poprzednim zadaniu
- podłączyć przycisk SW_1 do PB0
- podłączyć przycisk SW_2 do PB1
- zapoznać się jak elektronicznie są podłączone przyciski
- napisać program przycisku monostabilnego – przycisk SW_1 wciśnięty LED1 świeci, przycisk SW_1 niewciśnięty LED1 nie świeci
- napisać program przycisku bistabilnego – jedno wciśnięcie przycisku SW_2 LED2 świeci, drugie wciśnięcie przycisku SW_2 LED2 nie świeci
- powyższe funkcje działają w jednym programie
- jak działa program? czy jest jakiś problem? jak go rozwiązać?
6. Podsumowanie
Zadanie 6.1
- podłączyć diody jak w poprzednim zadaniu
- podłączyć przycisk SW_1 do PB0, …………, SW_4 do PB3
- napisać program:
- przyciskanie przycisku SW_2 powoduje zwiększania liczby świecących diod LED
- przyciskanie przycisku SW_1 powoduje zmniejszanie liczby świecących diod LED
- przyciśnięcie przycisku SW_3 powoduje wyłączenie wszystkich diod LED
- przyciśnięcie przycisku SW_4 powoduje włączenie wszystkich diod LED
- trzymanie wciśnięte przycisku nie powoduje zmiany
Zakres laboratorium 2017-2021 (nie aktualne)
Zakres laboratorium:
- Zapoznanie z płytką EDU
- Typy zmiennych dla mikrokontrolerów AVR
- Operacje bitowe
- Rejestry DDRx, PORTx, PINx – sterowanie portami
Zadania do wykonania:
- Zadanie 1.1
- Zadanie 2.1
- Zadanie 2.2
- Zadanie 4.1
1. Zapoznanie z płytką EDU (płytka EDU)
Do czego mogą służyć poszczególne elementy?
Dane techniczne mikrokontrolera ATmega32A:
- Rodzina: AVR
- Organizacja pamięci Flash: 32kb
- Pamięć EEPROM: 1024B
- Pamięć SRAM: 2048B
- Częstotliwość maksymalna: 16MHz
- Napięcie pracy: 2,7 – 5,5V
- Liczba wejść/wyjść: 32
- Liczba kanałów PWM: 4
- Liczba timerów 8-bit: 2
- Liczba timerów 16-bit: 1
- Obudowa: DIP40
Przykładowy wynik kompilacji:
Program – pamięć Flash
Data – pamięć SRAM (20% – minimalna wartość jaka powinna być dostępna)
Zadanie 1.1
- Utworzyć nowy projekt z plikami “GLOBAL.h” i “main.cpp” (szablony plików na stronie Środowisko programistyczne AVR). Podłączyć programator, skompilować i wgrać program do mikrokontrolera.
- Ile pamięci zajmuje ten program?
2. Typy zmiennych dla mikrokontrolerów AVR
Mikrokontrolery ATmega32 i ATmega328P są mikrokontrolerami 8-biotowymi (wszystkie rejestry mają pojemność 8 bitów). Oznacza to, że potrafią wykonywać proste operacje tylko na liczbach 8-biotowych, w celu wykonania operacji na większych liczbach mikrokontroler musi wykonać kilka instrukcji, co wpływa na wydajność programu. Dlatego też w pakiecie WinAVR można znaleźć specjalne typy zmiennych z dokładnym sprecyzowaniem rozmiaru zmiennej (stdint.h).
Typy zmiennych dla liczb dodatnich (bez znaku) i całkowitoliczbowych:
- uint8_t – 0 – 255
- uint16_t – 0 – 65535
- uint32_t – 0 – 4294967295
- uint64_t – 0 – 18446744073709551615
Zmienne z znakiem i całkowitoliczbowe:
- int8_t – -128 – 127
- int16_t – -32768 – 32767
- int32_t – -2147483648 – 2147483647
- int64_t – -9223372036854775808 – 9223372036854775807
Zmienne dla liczb zmiennopozycyjnych (liczba z przecinkiem) – typy zajmujące dużo pamięci:
- float (4 bajty)
- double (8 bajty, w Arduino 4 bajty)
Zmienne inne:
- char – zmienna 8 bitowa do przechowywania znaków
- bool – true/false – przechowuje wartość na pierwszej pozycji bajtu (0b00000001/0b00000000) ale i tak zajmuje 8 bitów.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <avr/io.h> #include <stdlib.h> #include <string.h> #include <util/delay.h> #ifndef _BV #define _BV(bit) (1<<(bit)) #endif #ifndef sbi #define sbi(reg,bit) reg |= (_BV(bit)) #endif #ifndef cbi #define cbi(reg,bit) reg &= ~(_BV(bit)) #endif #ifndef tbi #define tbi(reg,bit) reg ^= (_BV(bit)) #endif volatile uint8_t tab[100]; int main() { int i=0; while (1) { tab[1]=i++; _delay_ms(1000); } } |
Bity, bajty itd. – o co w tym chodzi???
uint8_t liczba= 209; uint8_t liczba= 0b11010001; uint8_t liczba= 0xD1;
int8_t liczba= -47; int8_t liczba= -0b11010001; int8_t liczba= -0xD1;
uint16_t liczba= 37585; uint16_t liczba= 0b1001001011010001; uint8_t liczba= 0x92D1;
Zadanie 2.1
- wyniki zapisywać na kartce w celu pokazania prowadzącemu
- przygotować i skompilować poniższy program
1 2 3 4 5 6 |
#include "GLOBAL.h" uint8_t zmienna[10]; int main() { zmienna[5]=5; } |
- ile pamięci zajmuje program przy rozmiarze tablicy 10?
- zmienić rozmiary tablicy na 100,1000,2000
- zanotować informacje o wykorzystanej ilości pamięci
- zmienić typ uin8_t na int i przeprowadzić takie same badania
Zadanie 2.2
- skompilować poniższy program:
1 2 3 4 5 6 7 8 |
#include "GLOBAL.h" volatile float zmienna; int main() { zmienna=5; zmienna+=4; } |
- ile pamięci zajmuje program, którzy wykorzystuje liczby zmiennoprzecinkowe
- zmienić typ liczby “zmienna” z float na uint8_t
- ile pamięci teraz zajmuje program, ile pamięci dodatkowo musi wykorzystać kompilator aby móc obsługiwać liczby zmiennoprzecinkowe?
Jakie wnioski można wysunąć? Przemyśleć i zaprezentować prowadzącemu wyniki i przedstawić wnioski.
3. Operacje bitowe
- Zaprzeczenie NOT: ~
- Iloczyn AND: &
- Suma OR: |
- Suma wyłaczająca XOR: ^
- Przesunięcie w prawo: >>
- Przesunięcie w lewo: <<
Zapoznać się z działaniem operacji: link1
W pliku “GLOBAL.h” znajdują się funkcje do operacji bitowych:
- sbi(rejestr,bit) – ustawia w rejestrze bit(pozycje) jako 1 – SET
- cbi(rejestr,bit) – ustawia w rejestrze bit(pozycje) jako 0 – CLEAR
- tbi(rejestr,bit) – ustawia w rejestrze bit(pozycje) na przeciwną wartość – TOGGLE
4. Rejestry DDRx, PORTx,PINx – sterowanie portami
Wyprowadzenia mikrokontrolera – sterowanie:
- Port A – PA0-PA7 – DDRA, PORTA, PINA
- Port B – PB0-PB7 – DDRB, PORTB, PINB
- Port C – PC0-PC7 – DDRC, PORTC, PINC
- Port D – PD0-PD7 – DDRD, PORTD, PIND
Rejestry – a o co chodzi?
Rejestr DDRx – rejestr kierunku:
- 0 – ustawia wyprowadzenie jako wejście
- 1 – ustawia wyprowadzenie jako wyjście
Rejestr PORTx – rejestr wyjściowy – gdy DDRx ==1:
- 0 – ustawia wyprowadzenie w stan niski
- 1 – ustawia wyprowadzenie w stan wysoki
Rejestr PORTx – rejestr wyjściowy – gdy DDRx ==0:
- 0 – nic nie robi (stan wysokiej impedancji)
- 1 – pull-UP – podciągnięcie wyprowadzenia do VCC przez rezystor 10k (zapobiega pojawianiu się nieokreślonych stanów na wyprowadzeniu)
Rejestr PINx – rejestr wejściowy – tylko do odczytu:
- 0 – oznacza stan niski na wyprowadzeniu
- 1 – oznacza stan wysoki na wyprowadzeniu
Podsumowując:
DDRx | PORTx | PINx* | |
Wyjście stan niski | 1 | 0 | |
Wyjście stan wysoki | 1 | 1 | |
Wejście wysoka impedancja | 0 | 0 | |
Wejście PULL-UP | 0 | 1 | |
Odczyt wejścia – stan wysoki | 0 | x | 1 |
Odczyt wejścia – stan niski | 0 | x | 0 |
*-tylko do odczytu
W celu ustawienia wyprowadzenie PD6 jako wyjście w stanie wysokim należy:
1 2 |
sbi(DDRD,PD6); //Ustawia wyprowadzenie PD6 do pracy jako wyjście sbi(PORTD,PD6); //Ustawia stan wysoki na wyprowadzeniu PD6 |
W celu ustawienia wyprowadzenie PD6 jako wyjście w stanie niskim należy:
1 2 |
sbi(DDRD,PD6); //Ustawia wyprowadzenie PD6 do pracy jako wyjście cbi(PORTD,PD6); //Ustawia stan niski na wyprowadzeniu PD6 |
Program realizujący miganie diodą:
1 2 3 4 5 6 7 8 9 10 11 |
int main() { sbi(DDRD, PD6); while (1) { sbi(PORTD, PD6); _delay_ms(500); cbi(PORTD, PD6); _delay_ms(500); } } |
Można też krócej:
1 2 3 4 5 6 7 8 |
int main() { sbi(DDRD, PD6); while (1) { tbi(PORTD, PD6); _delay_ms(500); } } |
Pierwsze kroki:
- podłączyć port D do linijki diodowej według schematu (jeśli zworki są w innym położeniu to należy je przepiąć):
- ustawić cały port D jak wyjścia – będzie służył do sterowania diodami. W celu uruchomienia diody należy ustawić stan niski na wyprowadzeniu mikrokontrolera.
Kilka sposobów:
- DDRD=0xFF;
- DDRD |= (1<<PD0) | … | (1<<PD7)
- sbi(DDRD, PD0) … sbi(DDRD, PD7)
Zadanie 4.1 – sterowanie wyprowadzeniami mikrokontrolera:
- włączyć wszystkie diody LED – jak można to zrobić, przetestować 3 sposoby
- napisać funkcję, która będzie mrugała wszystkimi diodami z częstotliwością 1s
- przygotować funkcje, która będzie realizowała zadanie “biegający punkt”. Jedna dioda na linijce diodowej jest włączona i porusza się w lewo lub w prawo – w zależności gdzie świecący punkt był wcześniej.
Zadanie domowe (wykonuje każda osoba samodzielnie):
- zapoznać się z tworzeniem schematów elektronicznych (Link2)
- narysować schemat elektroniczny na podstawie układu z zadania 4.1 (biegający punkt)
- schemat musi zawierać: mikrokontroler, podłączenie programatora, układ stabilizacji napięcia, diody LED z rezystorami
- schematy mogą być wykonane w programie komputerowym lub narysowane odręcznie,
- kompletne/prawidłowe schematy należy przynieść na kolejne zajęcia na kartce A4
Zadania na przyszłe laboratorium:
- Utrwalić wiadomości dotyczące obsługi portów (rejestr DDRx, PORTx, PINx – do czego służy, jak czytać?)
- Co to jest klawiatura matrycowa, jak obsługiwać? (klawiatura 4×4)
- Wyświetlacz LCD oparty o sterownik HD44780 – jak obsługiwać?
- Algorytm kalkulatora liczb całkowitych opartego o klawiaturę 4×4 (problem wczytywania liczb), wyświetlacz LCD (dodawanie, odejmowanie, mnożenie, wynik, anuluj)
* – http://apollo.astro.amu.edu.pl/PAD/index.php?n=Dybol.DydaktykaBinaria1