K U R S
Mikrokontrolery z rdzeniem
ARM, część 12
Porty GPIO
Budowa portów GPIO W poprzednich odcinkach zajmowaliśmy się
Mikrokontrolery LPC213x posia-
układami peryferyjnymi mającymi bezpośredni
dają dwa 32 bitowe porty wejścia/
wpływ na pracę rdzenia mikrokontrolera.
wyjścia P0 i P1, przy czym port
Omówiliśmy także przykładowy plik startowy
P1 ma wyprowadzone tylko najstar-
sze 16 bitów (P1.16& P1.31). Z por-
konfigurujący powyższe układy oraz
tu P0 nie ma wyprowadzonej linii
inicjalizujący pamięć mikrokontrolera zgodnie
P0.24, natomiast port P0.31 może
ze standardem ANSI C/C++.
pełnić tylko funkcję wyjścia. Porty
P0 i P1 są dwukierunkowe i mają Tematem bieżącego odcinka będą porty wejścia/wyjścia (GPIO)
maksymalną wydajność prądową
mikrokontrolerów LPC213x, które umożliwiają bezpośrednie
rzędu 45 mA zarówno od plusa
sterowanie układami podłączonymi do wyprowadzeń mikrokontrolera.
jak i minusa napięcia zasilającego.
W przypadku, gdy linie portu skon-
figurowane są jako wejściowe, port SEL0 (0xE002 C000) oraz PINSEL1 ce amatorskiej nie będziemy wyko-
P0 nie posiada rezystorów podcią- (0xE002 C004) natomiast port P1 rzystywać. Za sterowanie funkcjami
gających, natomiast port P1 wypo- z uwagi że ma wyprowadzonych alternatywnymi portu P1 odpowie-
sażony jest w rezystory podciągają- tylko 16 najstarszych bitów po- dzialny jest rejestr PINSEL2, który
ce o wartości 60& 300 kV. Każdy siada, jeden rejestr konfiguracyjny zawiera bity konfiguracyjne pokaza-
z pinów może pełnić również rolę PINSEL2 (0xE002 C014). Do kon- ne w tab. 18.
jednej z trzech funkcji alternatyw- figuracji każdego bitu portu P0 Po wyzerowaniu mikrokontro-
nych zapewniając podłączenie we- wykorzystywane są dwa bity z re- lera badany jest stan linii P1.26
wnętrznych układów peryferyjnych jestru PINSELx. Na rys. 29 przed- i P1.20. W przypadku, gdy linia
na przykład wyprowadzenie prze- stawiono budowę jednej linii portu P1.26 podczas zerowania znajdzie
twornika A/C. Podobnie jest w in- P0 (P0.1). się w stanie niskim wówczas in-
nych mikrokontrolerach, jak AVR W zależności od stanu bitów terfejs DEBUG jest włączany. Na-
czy 8051, gdzie każdy port wej- [3..2] rejestru PINSEL0 port linia tomiast, gdy linia P1.20 podczas
ścia/ wyjścia może również pełnić P0.1 mikrokontrolera pełni rolę zerowania będzie się znajdować
rolę wyprowadzenia wewnętrznego portu wejścia/wyjścia (00b), wejścia w stanie niskim, wówczas włączo-
układu peryferyjnego, jednak za- RxD pierwszego portu szeregowe- ny będzie interfejs TRACE. Ponie-
zwyczaj jest to pojedyncza funkcja. go (01b), wyjścia PWM (10b), lub waż linie portu P1 posiadają re-
Domyślnie po zerowaniu mikrokon- wejścia przerwania zewnętrznego zystory podciągające, pozostawienie
trolera wszystkie piny pełnią rolę (11b) Zastosowanie dodatkowych ich nie podłączonych spowoduje
portów I/O i są skonfigurowane rejestrów oraz multipleksera wybo- że domyślnie po zerowaniu inter-
w kierunku wejściowym. Za rolę, ru funkcji alternatywnej jest bardzo fejs DEBUG i TRACE będzie wy-
jaką pełni dane wyprowadzenie interesującym rozwiązaniem, ponie- łączony. Linie mikrokontrolera po
mikrokontrolera odpowiedzialne są waż nie musimy konfigurować por- zerowaniu domyślnie pracują jako
rejestry PINSELx. Port P0 posiada tów wejścia/wyjścia w odpowiednim porty I/O więc gdy chcemy sko-
dwa rejestry konfiguracyjne PIN- kierunku. Wybranie funkcji alter- rzystać z wybranych funkcji alter-
natywnej spowoduje automatyczne natywnych portu musimy odpo-
ustawienie linii portu w kierunku wiednio skonfigurować go poprzez
odpowiadającym pełnionej funkcji. zapis odpowiednich wartości do
W tab. 17 przedstawiono wszystkie rejestrów PINSELx. Na przykład,
funkcje, jakie mogą pełnić poszcze- jeżeli chcemy skorzystać z portu
gólne linie portu P0 wraz z odpo- szeregowego UART0, który wyko-
wiednią kombinacją bitów rejestrów rzystuje linie RxD0 i TxD0, należy
PINSEL0 oraz PINSEL1 potrzebną ustawić odpowiednie bity w reje-
do ustawienia odpowiedniej funk- strze PINSEL0:
#define PINTXD0 0x01
cji.
#define PINRXD0 (0x01<<2)
W przypadku portu P1 sytuacja
PINSEL0 |= PINTXD0 | PINRXD0; //Linie
jest dużo prostsza, ponieważ jedy-
P0.0 I P0.1 jako TxD0 i RxD0
ną funkcją alternatywną jaką pełni
ten port, jest interfejs debugowania Porty mikrokontrolerów LPC213x
Rys. 29. i śledzenia, którego raczej w prakty- w przeciwieństwie do mikrokontro-
Elektronika Praktyczna 11/2006
97
K U R S
spowoduje ustawienie wszystkich
Tab. 17. Funkcje pełnione przez poszczególne linie portu P0
linii portu P0 w stan niski.
Port Rejestr PINSELx 00b 01b 10b 11b
Rejestr IO0SET (0xE00028004)
oraz IO1SET (0xE00028014) umożli-
P0.0 PINSEL0 [1:0] P0.0 TxD0 PWM1
wia ustawienie wybranych linii I/O
P0.1 PINSEL0 [3:2] P0.1 RxD0 PWM3 EINT0
w stan wysoki ( 1 ) bez zmiany sta-
P0.2 PINSEL0 [5:4] P0.2 SCL0 CAP0.0
nu pozostałych linii. Na przykład
instrukcja IO0SET=0x80 spowoduje
P0.3 PINSEL0 [7:6] P0.3 SDA0 MAT0.0 EINT1
ustawienie P0.7 w stan wysoki bez
P0.4 PINSEL0 [9:8] P0.4 SCK0 CAP0.1 AD0.6
zmiany stanu pozostałych linii.
P0.5 PINSEL0 [11:10] P0.5 MISO0 MAT0.1 AD0.7
Rejestr IO0CLR (0xE00028004)
P0.6 PINSEL0 [13:12] P0.6 MOSI0 CAP0.2 AD1.0
oraz IO1CLR (0xE00028014) umoż-
liwia ustawienie wybranych linii
P0.7 PINSEL0 [15:14] P0.7 SSEL0 PWM2 EINT2
I/O w stan niski ( 0 ) bez zmiany
P0.8 PINSEL0 [17:16] P0.8 TxD1 PWM4
stanu pozostałych linii. Wpisanie
P0.9 PINSEL0 [19:18] P0.9 RxD1 PWM6 EINT3
1 na wybranym bicie powoduje
P0.10 PINSEL0 [21:20] P0.10 RTS1 CAP1.0 AD1.2 wyzerowanie odpowiadająego bitu
w porcie I/O. Na przykład instrukcja
P0.11 PINSEL0 [23:22] P0.11 CTS1 CAP1.1 SCL1
IO0CLR=0x80 spowoduje ustawie-
P0.12 PINSEL0 [25:24] P0.12 DSR1 MAT1.0 AD1.3
nie P0.7 w stan niski bez zmiany
P0.13 PINSEL0 [27:26] P0.13 DTR1 MAT1.1 AD1.4
stanu pozostałych linii.
Jak więc widzimy sterowanie
P0.14 PINSEL0 [29:28] P0.14 DCD1 EINT1 SDA1
portami I/O mikrokontrolera jest
P0.15 PINSEL0 [31:30] P0.15 RI1 EINT2 AD1.5
bardzo proste. Aby odczytać za-
P0.16 PINSEL1 [1:0] P0.16 EINT0 MAT0.2 CAP0.2
wartość linii wejścia/wyjścia mi-
P0.17 PINSEL1 [3:2] P0.17 CAP1.2 SCK MAT1.2 krokontrolera, wystarczy skonfigu-
rować wybraną linię jako wejścio-
P0.18 PINSEL1 [5:4] P0.18 CAP1.3 MISO MAT1.3
wą za pomocą rejestru kierunku
P0.19 PINSEL1 [7:6] P0.19 MAT1.2 MOSI CAP1.2
IOxDIR, a następnie odczytać stan
P0.20 PINSEL1 [9:8] P0.20 MAT1.3 SSEL EINT3
wybranej linii z rejestru IOxPIN.
Natomiast jeżeli chcemy ustawić
P0.21 PINSEL1 [11:10] P0.21 PWM5 AD1.6 CAP1.3
wybrane linie w odpowiedni stan
P0.22 PINSEL1 [13:12] P0.22 AD1.7 CAP0.0 MAT0.0
wystarczy za pomocą rejestru IO-
P0.23 PINSEL1 [15:14] P0.23
xDIR ustawić wybrane linie jako
P0.24 PINSEL1 [17:16]
wyjściowe i za pośrednictwem par
rejestrów IOxSET, IOxCLR lub IO-
P0.25 PINSEL1 [19:18] P0.25 AD0.4 AOUT
xPIN ustawić odpowiednie bity.
P0.26 PINSEL1 [21:20] P0.26 AD0.5
Zastosowanie rejestrów IOxSET
P0.27 PINSEL1 [23:22] P0.27 AD0.0 CAP0.1 MAT0.1
i IOxCLR jest bardzo wygodne po-
P0.28 PINSEL1 [25:24] P0.28 AD0.1 CAP0.2 MAT0.2 nieważ możemy ustawić lub ska-
sować wybrane bity portu bez
P0.29 PINSEL1 [27:26] P0.29 AD0.2 CAP0.3 MAT0.3
wcześniejszego ich odczytywania.
P0.30 PINSEL1 [29:28] P0.30 AD0.3 EINT3 CAP0.0
W przypadku, gdy chcemy zmie-
P0.31 PINSEL1 [31:30] P0.31 (Out)
nić całą zawartość danego portu,
wygodniej będzie skorzystać z re-
lerów rodziny 51 są w pełni dwu- padku, gdy wybrany pin skonfigu- jestru IOxPIN, który od razu usta-
kierunkowe. Do sterowania portami rowany jest jako wejściowy odczyt wi cały port zgodnie z zawartością
służą następujące rejestry SFR: tego rejestru jest bezpośrednim rejestru. Wszystko wygląda bardzo
Rejestr kierunku IO0DIR (0xE- odzwierciedleniem stanu sygna- kolorowo, jednak jest jeden drobny
00028008) (port P0), IO1DIR (0xE- łów elektrycznych panujących na mankament charakterystyczny dla
00028018) (port P1) umożliwia wy- tym pinie, natomiast, gdy wybra- mikrokontrolerów LPC213x. Mia-
bór kierunku pracy wybranej linii na linia skonfigurowana jest jako nowicie rejestry wejścia/wyjścia są
I/O. Ustawienie bitu w tym rejestrze wyjściowa, odczytanie tego reje- umieszczone w obszarze rejestrów
powoduje, że odpowiadająca mu stru powoduje odczytanie stanu VPB do których dostęp odbywa
linia I/O pełni rolę wyjścia, nato- wewnętrznych przerzutników portu się za pomocą stosunkowo wolnej
miast jego wyzerowanie powoduje, i odzwierciedla stan w jakim znaj- magistrali urządzeń peryferyjnych
że wybrana linia pełni rolę wejścia. duje się wybrana linia wyjściowa. VPB, w wyniku czego rdzeń po-
Na przykład wykonanie operacji Zapis do tego rejestru w przypad- trzebuje dodatkowych cykli, aby
IO0DIR=0x02; spowoduje ustawie- ku gdy wybrana linia skonfiguro- przesłać zawartość tego obszaru
nie linii P0.1 jako wyjściowej. wana jest jako wyjściowa powodu- pamięci do rejestru ogólnego prze-
Rejestr IO0PIN (0xE0028000) je wystawienie stanów logicznych znaczenia. Efektem tego jest bardzo
oraz IO1PIN (0xE0028010) umoż- odzwierciedlających stan rejestru wolny dostęp do porów I/O mikro-
liwia odczytanie oraz ustawienie na odpowiednich pinach mikro- kontrolera. Dla porównania AVR
stanu wybranej linii I/O. W przy- kontrolera. Na przykład IO0PIN=0, z zegarem 16 MHz potrafi szybciej
Elektronika Praktyczna 11/2006
98
K U R S
LED0 i wylacz LED1
Tab. 18.
LEDSET = LED0;
Bit Nazwa Opis Wart. pocz. LEDCLR = LED1;
}
[1:0] Zarezerwowane 0
Sprawdzanie wciśnięcia klawi-
0 Linie P1.26..P1.31 pracują jako porty IO
sza odbywa się poprzez odczy-
[2] GPIO/DEBUG 1 Linie P1.26..P1.31 skonfigurowane są jako port ~P1.26
tanie rejestru IO0PIN (KEYPIN),
DEBUG
natomiast włączanie i wyłączanie
0 Linie P1.25..P1.16 pracują jako porty IO
diod odbywa się poprzez wpisa-
[3] GPIO/TRACE 1 Linie P1.25..P1.16 skonfigurowane są jako port ~P1.20
nie jedynki na wybranym bicie
DEBUG
w rejestrze IO1SET (LEDSET) gdy
[31:4] Zarezerwowane
chcemy załączyć wybraną dio-
dę (linia portu przyjmie wówczas
zmieniać stan linii portu I/O niż co ułatwi pózniejsze zmiany oraz stan wysoki) i poprzez wpisanie
LPC213x pracujący z częstotliwo- wpłynie na większą przejrzystość jedynki na wybranym bicie w reje-
ścią 60 MHz. Konstruktorzy Philip- kodu: strze IO1CLR (LEDCLR) gdy chce-
#define LEDDIR IO1DIR //Rejestr ki- my wyłączyć wybraną diodę (linia
sa szybko zauważyli ten problem
erunku LED
i w mikrokontrolerach LPC214x ob- portu przyjmie wówczas stan ni-
#define LEDSET IO1SET //Rejestr
ustawiający bity LED
szar portów wejścia/wyjścia został ski). Sprawdzanie wciśnięcia stanu
#define LEDCLR IO1CLR //Rejestr
podłączony bezpośrednio do ma- klawisza S2 oraz załączenie diody
kasujący bity LED
gistrali lokalnej i porty te zostały #define LEDPIN IO1PIN //Rejestr LED1 i wyłączenie LED0 odbywa
portu LED
nazwane szybkimi portami GPIO. się w sposób analogiczny jak po-
W mikrokontrolerach LPC214x re- #define KEYDIR IO0DIR //Rejestr przednio. Dla pokazania sposobu
kierunku klawiszy
jestry te nie zostały bezpośrednio sterowania wyjściami za pomocą
#define KEYPIN IO0PIN //Rejestr
przeniesione pod nowy obszar, tyl- portu klawiszy rejestru IOxPIN działanie fragmen-
ko dodano nowy zestaw rejestrów, tu programu odpowiedzialnego za
#define LEDY (0xFF<<16) //Wszystkie
a stare rejestry dla kompatybilności LEDY P.16..P1.24 wykrycie wciśnięcia klawisza S3
#define LED0 (1<<16) //P1.16 Di-
wstecznej nadal znajdują się na jest trochę inne. Wykrywane jest
oda LED0
swoim miejscu. Sposób korzystania #define LED1 (2<<16) //P1.17 zbocze opadające na linii klawisza
Dioda LED1
z tych rejestrów zostanie przedsta- S3 (P0.6) poprzez porównanie bie-
#define LED2 (4<<16) //P1.18
wiony w ostatnim odcinku cyklu, Dioda LED2 żącego i poprzedniego stanu klawi-
który będzie poświecony w całości sza:
#define S1 0x10 //P0.4 Klawisz S1
nowemu LPC214x. #define S2 0x20 //P0.5 Klawisz S2 //Jezeli zbocze opadajace na S3 to
#define S3 0x40 //P0.6 Klawisz S3
zmien stan LED2
key = KEYPIN & S3;
Trochę praktyki przykładowy Działanie programu rozpoczyna
if(pkey && !key)
program się w funkcji main od ustawie- {
LEDPIN ^= LED2;
Mając już odpowiednią dawkę nia rejestrów kierunku. Bity portu
}
wiedzy teoretycznej zajmiemy się P1.16& P1.18 odpowiedzialne za
pkey = key;
teraz napisaniem prostego progra- sterowanie diodami LED ustawia- W przypadku, gdy zostanie wy-
mu mającego na celu zapozna- ne są w kierunku wyjściowym, na- kryte zbocze, stan linii P1.18
nie się z rejestrami portów GPIO. tomiast linie portu do których są (LED2) jest zmieniany na przeciw-
Oczywiście i w tym przypadku bę- podłączone klawisze S1, S2, S3 ny za pomocą operacji XOR na re-
dziemy korzystać z zestawu uru- ustawiane są jako wejściowe: jestrze LEDPIN (P1PIN).
chomieniowego ZL6ARM. Działanie //Kierunek dla ledow wyjście Uruchamiając ten program mo-
LEDDIR |= LEDY;
programu będzie następujące: po żemy zauważyć że nie jest on od-
//Kierunek dla klawiszy wejście
wciśnięciu przycisku S1 zostanie porny na drgania styków. Nie ma
KEYDIR &= ~(S1|S2|S3);
zapalona dioda LED0 oraz wyłą- Operacja ustawienia linii portu to znaczenia w przypadku reakcji
czona dioda LED1; po wciśnięciu P0.4...P0.6 nie są niezbędne, po- na klawisz S1 i S2, ponieważ gdy
przycisku S2 dioda LED0 zgaśnie, nieważ po wyzerowaniu mikrokon- linia danego portu jest już skaso-
natomiast dioda LED1 zostanie za- troler ustawia wszystkie linie I/O wana lub ustawiona, to ponowne
palona. Po wciśnięciu klawisza S3 w kierunku wejściowym. Tak samo skasowanie lub ustawienie tej sa-
stan diody LED3 zostanie zmienio- w programie tym nie ustawiamy mej linii nie spowoduje żadnych
ny na przeciwny. W programie tym w ogóle bitów rejestru PINSELx, efektów. Natomiast w przypadku
wykorzystamy omawiane we wcze- ponieważ domyślnie po zerowaniu wciśnięcia klawisza S3 stan linii
śniejszych odcinkach pliki starto- do linii wejściowych podłączone portu zmieniany jest na przeciw-
we, dlatego nie będziemy się już są porty GPIO. Po tej czynności ny. Możemy więc zauważyć wielo-
nimi tutaj zajmować. Przykładowy program wchodzi do pętli nieskoń- krotne zmiany stanu diody LED2.
program można także ściągnąć ze czonej while(1){& }, w której Modernizację programu tak, aby
strony EP (ep5a.zip) i zaimporto- sprawdzane jest wciśnięcie klawi- był odporny na drgania zestyków
wać do środowiska Eclipse. Pisanie sza S1 (stan niski) i w przypadku pozostawiam Czytelnikowi jako
programu rozpoczynamy od zdefi- jego naciśnięcia włączana jest dio- ćwiczenie do samodzielnego wyko-
niowania stałych odpowiadających da LED0 oraz wyłączana LED1: nania.
bitom poszczególnych diod, przy- Lucjan Bryndza, EP
if(!(KEYPIN & S1))
{
cisków oraz portów do których są lucjan.bryndza@ep.com.pl
//!Jezeli wcisniety S1 to zalacz
podłączone diody LED i klawisze,
Elektronika Praktyczna 11/2006
99
K U R S
gramowania obiektowego C++. Nie do 120 ms poza rozkazem czysz-
będziemy tutaj szczegółowo oma- czenia wyświetlacza który może za-
Program drugi wyświetlacz wiać aspektów działania wyświe- jąć maksymalnie 4,8 ms. Za obsłu-
LCD tlacza LCD, a zainteresowanych od- gę LCD odpowiedzialna jest klasa
Kolejnym programem jaki na- syłam do EdW 11/97. W zestawie CLcdDisp, której deklaracja znajduje
piszemy w ramach ćwiczeń z por- ZL6ARM linie D0.D7 LCD podłą- się w pliku CLcdDisp.h natomiast
tami GPIO będą procedury obsłu- czone są do portu P1.16& P1.23. definicja została umieszczona w pli-
gi znakowego wyświetlacza LCD Linia E podłączona jest do portu ku CLcdDisp.c. Metody (funkcje)
(HD44180). Procedury te będziemy P0.30 natomiast RS do portu P0.31. i obiekty (zmienne) zadeklarowane
intensywnie wykorzystywać w dal- W zestawie niestety nie przewidzia- z modyfikatorem private mogą być
szej części kursu. W różnych cza- no możliwości sterowania linią R/ używane tylko wewnątrz klasy, co
sopismach o tematyce elektronicznej W przez co niemożliwe jest odczy- zapewnia ukrycie ich przed użyt-
obsługa znakowego wyświetlacza tywanie stanu wyświetlacza, dlatego kownikiem końcowym. W sekcji tej
LCD była poruszana wielokrotnie. po wysłaniu każdego znaku i rozka- zapisano stałe związane z wyświe-
Dlatego aby nie powielać tych sa- zu musimy odczekać pewien okres tlaczem LCD takie jak przypisanie
mych schematów tym razem biblio- czasu tak aby wybrana operacja bitów odpowiedzialnych za linię E
teka ta zostanie napisana w nieco została wykonana. Prawie wszystkie i RW wyświetlacza oraz stałe zwią-
odmienny sposób za pomocą pro- komendy wykonywane są w czasie zane z komendami kontrolera LCD.
int mx;
Klasy, Obiekty, oraz programowanie zorientowane finicji klasy zamknęliśmy wszelkie pola i meto-
obiektowo w skrócie dy klasy co nazywamy enkapsulacją danych.
};
Pisząc w języku C program, który dotyczy jakiś re- W deklaracji klasy znajdują się także specyfika- Wówczas metoda ta zostanie potraktowana jako
alnych obiektów na przykład regulatora temperatury, tory dostępu public, protected, private. Etykieta
metoda inline i zostanie rozwinięta w miejscu
tablicy świetlnej, sterownika akwariowego musimy private oznacza że pola i metody znajdujące
wywołania. Metody zawierające więcej niż kilka
wszelkie zależności i wielkości zamienić na zestaw się pod nią dostępne są tylko z wnętrza klasy.
linijek kodu powinny być zadeklarowane w pli-
luznych liczb i funkcji operujących na danych. Na Etykieta protected oznacza że pola i metody
kach *.c. w sposób następujący.
Zwracany_typ Nazwa_klasy::NazwaMetody(ar-
przykład w zmiennej float temp trzymamy tem- znajdujące się pod nią są dostępne dla klas
gumenty)
peraturę bieżącą, przy czym tylko my wiemy że dziedziczonych od tej klasy. (O dziedziczeniu bę- {
//Ciało metody
jest to temperatura zadana. Równie dobrze liczbę dziemy jeszcze mówić przy innej okazji) . Nato-
}
reprezentującą temperaturę moglibyśmy podstawić miast etykieta public oznacza że pola i metody
Widzimy że nazwę metody poprzedza nazwa
do jakiejś innej funkcji realizującą całkiem inne dostępne są wewnątrz jak i na zewnątrz klasy.
klasy zakończona specyfikatorem dostępu :: co
zadanie a kompilator nawet nie zaprotestowałby Zastosowanie specyfikatorów dostępu pozwala
określa że dana metoda należy do danej klasy.
tylko wyliczyłby jakieś bzdury. Natomiast otaczający ukryć przed użytkownikiem końcowym wszelkie
Na przykład we wspomnianym wcześniej przy-
nas świat nie składa się z luznych liczb i funkcji mechanizmy wewnętrzne klasy. Po prostu użyt-
kładzie klasy mojaklasa zadeklarowanie metody
tylko z obiektów. Na przykład wspomniany regulator kownik korzystający np. z klasy wyświetlacza
klasy w pliku *.c wygląda następująco:
temperatury jest obiektem, który z kolei zawiera LCD nie powinien mieć dostępu do metody
int mojaklasa::metoda1(int a)
{
w sobie obiekty takie jak wyświetlacz LCD, czujnik przesyłającej na magistralę bajt danych, metoda
return a*a + mx;
temperatury, czy klawiaturę. Właśnie język C++ ta powinna być wywoływana tylko przez inne
}
pozwała nam działać w sposób obiektowy umożli- metody z wnętrza klasy. W sekcji public widzimy
To o czym wcześniej mówiliśmy było tylko de-
wiając budowanie modeli rzeczywistych obiektów, metodę której nazwa jest identyczna jak nazwa
finicją klasy określającą sposób w jaki ona była
a nie luznego zestawu liczb oraz funkcji. Każdy klasy jest to tak zwany konstruktor klasy, który
zbudowana. Sama definicja klasy nie deklaru-
model posiada zestaw danych (pól) oraz zachowań jest specjalną metodą wywoływaną w momencie
je żadnych obiektów (egzemplarzy) tej klasy.
(metod). Na przykład wyświetlacz LCD posiada tworzenia obiektu danej klasy. Umożliwia nam
Utworzenie konkretnych obiektów danej klasy
dane w postaci tekstu do wyświetlenia oraz zacho- to wykonanie pewnych czynności zanim obiekt
odbywa się w taki sam sposób jak tworzenie
wania (metody) takie jak wyczyszczenie wyświetla- danej klasy powstanie. Np. tworząc obiekt klasy
obiektów typów wbudowanych np. int a,b,c,d;
cza, wypisanie liczby, czy przesunięcie kursora na wyświetlacz LCD w konstruktorze będziemy ini- spowoduje utworzenie 4 obiektów typu int o na-
wskazaną pozycję. Zbierając te wszystkie dane i za- cjalizować wyświetlacz tak aby był on w stanie
zwach a b c d. Tak samo napisanie mojaklasa
chowania w jedną całość budujemy konkretny typ wyświetlać znaki. Tworząc na przykład klasę
a,b,c,d; spowoduje utworzenie czterech obiek-
(klasę) wyświetlacza LCD. Wymyśliliśmy więc opis pojemnika na liczby w konstruktorze tej klasy
tów o nazwach a b c d klasy mojaklasa. Należy
umożliwiający zbudowanie konkretnego egzemplarza alokować będziemy pamięć do przechowywa- sobie uzmysłowić że utworzenie 4 obiektów
(obiektu) wyświetlacza LCD, nie jest to jeszcze ża- nia tych liczb. W konstruktorze nie ma żadnej
klasy mojaklasa spowoduje utworzenie 4 od-
den konkretny wyświetlacz. Definicja klasy w języku magii jest to po prostu zwykła funkcja której
dzielnych kompletów danych dla poszczególnych
C++ ma następującą postać: osobliwością jest to że jest ona wywoływana
obiektów danej klasy. Natomiast metody operu-
class budowany_typ
w momencie tworzenia obiektu danej klasy. Ana- jące na tych składnikach definiowane są tylko
{
public: // logiczną funkcją do konstruktora, wywoływaną
jednokrotnie. Każda metoda do pól danej klasy
Specyfikator dostepu
w momencie niszczenie obiektu danej klasy jest
odwołuje się za pomocą wskaznika this, który
budowany_typ();
//Konstruktor klasy destruktor klasy wywoływany w momencie gdy
pokazuje na konkretny egzemplarz danej klasy.
~budowany_typ();
obiekt danej klasy przestaje istnieć. Destruktor
Na przykład we wspomnianej wcześniej meto-
//Destruktor klasy
metoda1(); deklarujemy poprzedzając metodę o takiej samej
dzie metoda1() odwołanie do pola mx będącego
nazwie jak klasa znakiem ~. Na przykład we
składnikiem danej klasy odbywa się za pomo-
metoda2();
wspomnianym wcześniej pojemniku na liczby
cą wskaznika this następująco: return a*a +
protected: //Spe-
destruktor będzie zawierał funkcje dealokacji
this >mx. Wskaznik ten jest tutaj wywoływany
cyfikator dostepu
metoda3(); pamięci którą wcześniej przydzieliliśmy w kon- niejawnie przez kompilator, ale my czasami bę-
struktorze. Definicję klasy najczęściej tworzymy
dziemy z niego świadomie korzystać. Odwołanie
private: //Spe-
cyfikator dostepu w plikach nagłówkowych *.h. Natomiast deklara- do wybranego pola danej klasy odbywa się
int pole1;
cję poszczególnych metod możemy zawrzeć we
za pomocą znaku kropki. Na przykład wpisanie
float pole2;
wnętrzu definicji ciała samej klasy np.
}; b=a.mx spowoduje przepisanie pola mx obiektu
class mojaklasa
Zdefiniowane własnej klasy nie jest trudne naj- a do zmiennej b. Natomiast wywołanie metod
{
na rzecz konkretnego obiektu odbywa się po-
pierw występuje tutaj słowo kluczowe class na- public: //
Specyfikator dostepu
stępnie występuje nazwa klasy po czym klamra przez wpisanie po kropce danej metody. Na
int metoda1(int a)
a w niej ciało klasy. W ciele klasy deklarujemy przykład c.metoda1(4) spowoduje wywołanie
{
wszelkie metody (zachowania) i pola (dane) kla- metoda1() działającej na danych będących skła-
return a*a + mx;
dowymi obiektu b.
sy. Jest to bardzo ważny aspekt bowiem w de- }
Elektronika Praktyczna 11/2006
100
K U R S
if(cmd) LCDCCLR = RS;
//Funkcja opozniajaca void CLcdDisp::Delay(unsigned int
else LCDCSET = RS;
void Delay(unsigned int del); del)
//Wysyla do portu {
void PortSend(unsigned char asm volatile
Następnie na linii E generowany
data,bool cmd=false); (
//Pin E P0.30 dloop%=:
jest dodatni impuls w wyniku które-
static const unsigned int E = subs %[del],%[del],#1\t\n
go następuje zapisanie danych lub
0x40000000; bne dloop%=\t\n
//Pin RW P0.31 : :[del] r (del)
instrukcji do wyświetlacza LCD.
static const unsigned int RS = );
//Ustaw Enable
0x80000000; }
LCDCSET = E;
//Maska danych
Metoda PortSend służy do wysy-
Delay(DELAY_HW);
static const unsigned int DMASK =
//Skasuje enable
0x00FF0000; łania pojedynczego bajtu danych do
LCDCCLR = E;
//Domyslne sprzetowe
wyświetlacza LCD została ona zade-
static const unsigned int DELAY_HW
= 15;
klarowana następująco: Wszystkie metody zadeklarowane
//Opoznienie komend
void PortSend(unsigned char data,bo- jako public dostępne są dla użyt-
static const unsigned int DELAY_CMD
ol cmd=false);
= 3000;
kownika i stanowią zewnętrzny in-
//Opoznienie dla CLS
Jako parametr data przekazujemy terfejs klasy. Klasa CLcdDisp zawie-
static const unsigned int DELAY_CLS
= 30000;
instrukcję lub daną którą chcemy ra następujące składowe publiczne:
//Komendy wyswietlacza
public:
wysłać do wyświetlacza LCD. Gdy
enum {CLS_CMD=0x01,HOME_
CLcdDisp();
CMD=0x02,MODE_CMD=0x04,ON_CMD=0x08,
parametr cmd przyjmie wartość fal- ~CLcdDisp();
SHIFT_CMD=0x10,FUNC_CMD=0x20,CGA_
void Write(const char *str);
CMD=0x40,DDA_CMD=0x80}; se oznacza to, że liczba przekaza-
void Write(char zn);
//Komenda MODE
na jako data zinterpretowana będzie void Write(unsigned int licz);
enum {MODE_R=0x02,MODE_L=0,MODE_
//Wyczysc wyswietlacz
MOVE=0x01};
jako znak do wyświetlenia, w prze-
void Clear(void);
//Komenda SHIFT
ciwnym przypadku przesłana dana //Zalacz wylacz kursor
enum {SHIFT_DISP=0x08,SHIFT_
void SetCursor(unsigned char cmd);
R=0x04,SHIFT_L=0};
stanowić będzie rozkaz. W języku
void GotoXY(unsigned char
//Komenda FUNC
x,unsigned char y);
C++ możemy deklarować metody
enum {FUNC_8b=0x10,FUNC_4b=0,FUNC_
template
CLcdDisp& opera-
2L=0x08,
i funkcję z parametrami domyślny-
tor <<(T obj)
FUNC1L=0,FUNC_5x10=0x4,FUNCx7=0};
{
}; mi. W przypadku gdy wywołamy
Write(obj);
Umieszczono tu także dwie me- funkcję bez drugiego argumentu pa- return *this;
}
tody: Delay, odpowiedzialną za ge- rametr cmd przyjmie wartość false,
CLcdDisp& operator <<(pos obj)
nerowanie opóznień, oraz PortSend natomiast gdy drugi parametr bę- {
GotoXY(obj.mx,obj.my);
wysyłającą bajt danych do wyświe- dzie określony podczas wywołania
return *this;
tlacza Lcd. Pętla opózniająca zosta- argument domyślny będzie ignoro- }
ła napisana w asemblerze, aby było wany. Mechanizm ten został stwo- CLcdDisp jest domyślnym kon-
możliwe dokładne określenie czasu rzony w celu zastąpienia funkcji ze struktorem klasy i jest on wywo-
jej wykonania. Jako argument meto- zmienną listą argumentów (& ) zna- ływany podczas tworzenia nowego
dy podajemy liczbę która następnie ną z języka C, pozwala on zapew- obiektu danej klasy. W konstrukto-
jest ładowana do któregoś z reje- nić większą kontrolę nad przekazy- rze napisano procedurę inicjaliza-
strów ogólnego przeznaczenia w któ- wanymi argumentami. Działanie tej cji wyświetlacza LCD. Inicjalizacja
rym następuje cykliczne odejmowa- metody jest następujące: Najpierw rozpoczyna się od ustawienia linii
nie liczby jeden, aż do momentu sygnał E ustawiany jest w stan 0 RS,E i D0..D7 oraz odczekania kil-
gdy rejestr ten osiągnie wartość 0. w efekcie czego wyświetlacz ignoru- kudziesięciu milisekund na ustabili-
je wszystkie stany pojawiające się zowanie napięcia zasilającego:
//Konstruktor klasy obslugi wyswie-
Przeładowanie nazw funkcji i metod na liniach danych wyświetlacza.
tlacza LCD
Programując w języku C przyzwyczailiśmy się
Linie D0..D7 wyświetlacza LCD są
CLcdDisp::CLcdDisp()
że w programie może być tylko jedna funkcja
{
zerowane poprzez ustawienie bitów
o takiej samej nazwie. W języku C++ nato- //Linie E i RS jako wyjsciowe
16.23 w rejestrze IO1CLR. Do portu
LCDCDIR |= E|RS;
miast może istnieć więcej niż jednak funkcja
LCDCCLR = E|RS;
IO1SET przesyłana jest zawartość
lub metoda w obrębie klasy posiadająca taką
//Linia danych jako wyjsciowa
samą nazwę pod warunkiem że posiada ona zmiennej data przesuniętej o 16 bi- LCDDDIR |= DMASK;
Delay(100000);
inną listę argumentów. Inaczej rzecz mówiąc
tów w lewo. W wyniku tych dwóch
kompilator C++ rozpoznaje funkcje lub me-
operacji linie P1.16..P1.23 przyj- Następnie trzykrotnie wysyłana
todę nie tylko po samej nazwie ale też po
mują wartość zgodną z zawartością jest komenda ustawiająca wyświe-
liście argumentów. Na przykład w C gdybyśmy
chcieli napisać funkcję do wyświetlania po- zmiennej data bez zmiany pozosta- tlacz w tryb 8 bitowy
PortSend(FUNC_CMD|FUNC_8b,true);
szczególnych typów danych na wyświetlaczu
łych bitów portu.
Delay(DELAY_CLS);
LCD musielibyśmy dla każdego typu zdefinio-
//E=0
PortSend(FUNC_CMD|FUNC_8b,true);
wać funkcję o innej nazwie: WriteInt(int w); LCDCCLR = E;
Delay(DELAY_CMD);
//Data = 0;
WriteStr(char *s); WriteChar(char c); W mo- PortSend(FUNC_CMD|FUNC_8b,true);
LCDDCLR = DMASK;
Delay(DELAY_CMD);
mencie gdy chcieliśmy wypisać konkretny typ
//Wyslij dane
po czym następuje ustawie-
danej na przykład int musieliśmy wywołać
LCDDSET = ((unsigned int)data) <<
16;
funkcję WriteInt(). W języku C++ możemy na-
nie wyświetlacza tak aby praco-
tomiast zdefiniować trzy funkcje o takiej samej
Po przesłaniu danych na linię wał w rozdzielczości 5x7 załączenie
nazwie Write z inną listą argumentów np. tak:
D0..D7 następuje ustawienie linii wyświetlacza, wyczyszczenie oraz
Write(int w); Write(char *s); Write(char c);
RS w odpowiedni stan w zależno- ustawienie kurosa w pozycji po-
W momencie wywołania funkcji nie musimy
się zastanawiać którą wersję funkcji chce- ści od tego czy dane przesłane na czątkowej. Kolejnymi metodami pu-
my wywołać po prostu piszemy Write( Text )
magistrale zinterpretowane zostaną blicznymi są metody Write służące
a kompilator sam na podstawie listy argumen-
jako rozkaz (stan wysoki) albo znak do wypisania na wyświetlaczu po-
tów ustali że trzeba wywołać funkcję Write-
do wyświetlenia (stan niski) jedynczego znaku, łańcucha teksto-
(char *s);
//Skasuj lub ustaw RS
wego, oraz liczby stałoprzecinkowej.
Elektronika Praktyczna 11/2006
101
K U R S
Write(obj);
Uważnego czytelnika może zdziwić da Write której argument jest typu
return *this;
}
fakt, że metody o takiej samej na- int. Poszczególne metody są bardzo
zwie zadeklarowane są kilkukrotnie. podobne przedstawię tutaj metodę Zastosowano tutaj kolejną cechę
Jest to kolejna zaleta języka C++, Write wypisująca łańcuch tekstowy. języka C++ mianowicie funkcję
w którym możemy deklarować funk- void CLcdDisp::Write(const char
wzorcową. Mechanizm ten umoż-
*str)
cję i metody o takich samych na- liwia zadeklarowanie tylko jednej
{
while(*str)
zwach. Kompilator w zależności od funkcji niezależnie od argumen-
{
argumentu przekazanego do meto- tów jakie ona przyjmuje. Po pro-
PortSend(*str++);
Delay(DELAY_CMD);
dy wywoła odpowiednią funkcje stu w momencie wywołania funkcji
}
Write. Np. jeżeli napiszemy lcd. } z danym parametrem, kompilator
Write(100) wywołana zostanie meto- Działanie tej metody polega na etapie kompilacji tworzy daną
na odczytaniu pojedynczego zna- funkcję zamieniając T na konkret-
Przeładowanie operatorów
ku, przepisaniu jego zawartości ny typ danych na przykład. int.
W języku C++ istnieje możliwość zdefiniowa-
do wyświetlacza LCD za pomo- W wyniku tej czynności nie musi-
nia własnych operatorów czyli możemy spra-
cą metody PortSend, oraz odcze- my pisać trzech osobnych wersji
wić żeby znaczki takie jak +, ,*,/ wykonywały
dla nas jakieś czynności na rzecz tworzonych
kaniu około 40 ms na przesłanie operatora dla każdego typu danych:
przez nas klas. Możemy na przykład spra-
znaku. Następnie wskaznik zwięk- char*, int, char. Działanie operatora
wić że operator pełniący rolę przesunięcia
szany jest o jeden i wysyłany jest << jest bardzo proste mianowicie
bitowego w stosunku do wbudowanych typów
kolejny znak. Dzieje się tak do parametr który otrzymuje operator
danych << dla klasy wyświetlacza LCD bę-
czasu gdy zostanie wykryty znak przekazywany jest do funkcji Wri-
dzie wypisywał znaki na ekranie. W przypadku
wbudowanych typów danych na przykład int,
0 będący symbolem końca łań- te, która wypisuje w odpowiedni
gdy wpiszemy a*b kompilator po prostu wy-
cucha. Metoda Clear() umożliwia sposób dane na wyświetlaczu LCD.
woła specjalną funkcję powodująca pomnożenie
wyczyszczenie zawartości wyświe- Operator zwraca wskaznik do klasy
dwóch argumentów a i b. Podobnie stanie się
tlacza, natomiast metoda GotoXY() obiektu LCD co umożliwia tworze-
na przykład gdy a i b będą zdefiniowane jako
double zostanie wówczas wywołana funkcja umożliwia przejście do wybranej nie operacji łańcuchowych. W pro-
mnożenia dwóch liczb typu double. W C++
pozycji kursora. Nie będę ich tu- gramie stworzono także dodatko-
możemy zdefiniować własne wersje dowolnego
taj przedstawiał ponieważ odbywa wą klasę pos, której przesłanie do
operatora które wykonują jakieś czynności na
się to na zasadzie wysłania odpo-
stworzonych przez nas typach danych (kla-
wiedniego kodu komendy oraz od-
Funkcje i metody wzorcowe
sach). Na przykład gdy mamy obiekty nasza-
Gdybyśmy chcieli zapisać bardzo prosty algo-
klasa a,b; i napiszemy a*b kompilator wywoła
czekania określonego czasu na jej
rytm wyliczający na przykład minimum mu-
naszą funkcję operatorową *, która wykona
wykonanie. Czytelnicy którzy pro-
sielibyśmy dla każdej pary argumentów (np.
jakąś operację na naszym obiekcie. (Oczywi-
gramowali w języku C++ zapewne
int, float, double itd.) stworzyć oddzielne wer-
ście jeżeli została ona wcześniej zdefiniowana).
korzystali z biblioteki standardowej
sje funkcji min(), co niepotrzebnie komplikuje
Operator może być napisany jako oddzielna
iostream która umożliwiała wypi- i wydłuża program. W języku C++ istnieje
funkcja lub jako metoda składowa klasy. Na
mechanizm funkcji i metod wzorcowych w któ-
przykład operator dodawania dla własnego
sywanie komunikatów i zmiennych
rym zamiast z góry określać typy argumen-
typu danych zdefiniowany jako funkcja ma
na ekran poprzez wpisanie da-
tów i zwracane wartości, można niektóre lub
następującą postać:
nych do obiektu cout Na przykład:
mojtyp operator+(mojtyp a,mojtyp b)
wszystkie z tych typów zastąpić parametrami,
{
cout << Zmienna= <<
natomiast sama treść funkcji nie zmieni się.
return a+b;
}
Na przykład wspomniana wcześniej funkcja
Zmienna << endl; Przesyłanie
Taki sam operator dodawania możemy zadekla-
wyliczająca minimum wygląda następująco:
danych do obiektu odbywa się za
template Typ min(Typ a,Typ b)
rować jako metodę składową klasy:
{
mojtyp mojtyp::operator+(mojtyp b) pomocą operatora <<. W języku
return a{
C++ możemy zmieniać znaczenie
}
return this >a + b;
}
operatorów, co nosi nazwę przecią- Definicję funkcji wzorcowej poprzedza słowo
Widzimy że w tej definicji zniknął jeden argu-
kluczowe template po którym następuje lista
żania operatorów. Korzystając z tej
ment, ponieważ funkcja jest teraz składową
parametrów formalnych oddzielonych przecin-
techniki napiszemy własne wersję
klasy to znaczy że jest wykonywana na rzecz
kami. Każdy parametr składa się ze słowa
operatora << umożliwiające wypi-
konkretnego obiektu, zatem dostaje do niego
kluczowego class określającym że typem może
wskaznik this do obiektu który jest właśnie
sywanie liczb i zmiennych na przy- być zarówno typ wbudowany jaki klasa zde-
pierwszym argumentem funkcji operatorowej.
finiowana przez użytkownika. Zadeklarowany
kład tak lcd << Zmienna=
W ten sposób możemy również przeładowywać
w ten sposób parametr formalny może być
<< zm; Napiszemy także bardzo
inne operatory. Musimy tylko pamiętać że nie
używany jak typ wbudowany lub klasa użyt-
prostą klasę pos której przekazanie
możemy zmienić znaczenia operatorów dla
kownika w pozostałej części funkcji wzorcowej.
typów wbudowanych na przykład int. Bardzo do obiektu klasy wyświetlacza LCD
Dalsza deklaracja funkcji nie różni się niczym
ważną informacją jest również ze priorytety
spowoduje przesunięcie kursora na od zwykłych niewzorowych funkcji. W po-
operatorów są zawsze takie same i ściśle
wyższym przykładzie parametr Typ służy do
wybraną pozycję np. tak lcd <<
określone i nie możemy zmieniać priorytetów
określenia typu wartości przekazywanych do
pos(1,2) << 2 linia ;
operatorów.
funkcji min oraz wartości zwracanej przez nią.
Wszystkie operatory korzystają
Zrozumienie mechanizmu definiowania operato-
Za każdym razem gdy funkcja min() zostanie
rów dla własnych typów klas będzie łatwiejsze
z wcześniej zdefiniowanych metod
użyta w miejsce parametru Typ podstawiony
gdy zobaczymy w jaki sposób odbywa się to
Write() oraz GotoXY() i są zdefinio- zostanie odpowiedni dla danego przypadku typ
dla jakiegoś typu wbudowanego. Na przykład
wbudowany np. gdy wpiszemy min(10.0,12.0)
wane w deklaracji klasy zapewniając
gdy mamy zdefiniowane dwie zmienne float
za parametr Typ zostanie podstawiony typ
ich rozwinięcie ich w miejscu wy-
a=12; float b=15; i wpiszemy a*b wówczas
wbudowany float. Proces prowadzący do pod-
wołania. Operator wysyłający dane
zostanie wywołana funkcja operator*(a,b) która
stawienia właściwego typu nazywa się konkre-
w gdzieś tam we wnętrzu kompilatora zdefinio- do strumienia zdefiniowano w spo- tyzowaniem wzorca. Po prostu kompilator na
wana jest następująco:
podstawie wzorca sam stworzy sobie odpo-
sób następujący:
float operator*(float a,float b)
wiednią wersję funkcji operującą na określo-
{
template CLcdDisp& operator
return a*b;
nym typie danych w tym przypadku float.
<<(const T &obj)
}
{
Elektronika Praktyczna 11/2006
102
K U R S
}
klasy wyświetlacza LCD spowoduje do odpowiedniej pozycji zawartej
ustawienie kursora na wybranej po- w zmiennych mx,my. Działanie programu rozpoczyna
CLcdDisp& operator <<(const pos
zycji. Definicja tej klasy jest nastę- się od utworzenia obiektu klasy
&obj)
pująca: CLcdDisp o nazwie cout. W funkcji
{
GotoXY(obj.mx,obj.my);
class pos
main() wypisywany jest napis powi-
return *this;
{
} talny, a następnie program wchodzi
public:
pos(unsigned char x,unsigned char
W pliku testlcd.cpp znajduje się w pętlę nieskończoną, która odczytu-
y):mx(x),my(y) {}
bardzo prosty programik korzystają- je stan klawiszy S1..S4 oraz przepi-
unsigned char mx,my;
};
cy z klasy CLcdDisp, wypisujący na suje ich zawartość do zmiennej sk
Klasa ta zawiera tylko dwa pola wyświetlaczu LCD stan wciśniętego maskując pozostałe nie istotne bity.
określające pozycje kursora na wy- klawisza S1..S4. Następnie na pozycji 8,2 wypisywa-
CLcdDisp cout;
świetlaczu, oraz konstruktor który ny jest stan zmiennej sk. Pomimo,
przyjmuje jako argumenty pozycję że mechanizmy tworzące operatory
//Funkcja glowna main
kursora oraz przepisuje je do mx są trochę zawiłe korzystanie z samej
int main(void)
oraz my. biblioteki obsługi wyświetlacza LCD
{
cout << Witaj ! ;
Dla obiektu klasy pos stworzo- jest bardzo proste. Czytelnikom
cout << pos(1,2) << IO0PIN= ;
ny jest osobny operator << który unsigned int sk; znającym język C++ proponuję na-
while(1)
wywołuje metodę GotoXY() prze- pisanie klasy o nazwie clear której
{
suwając kursor wyświetlacza LCD sk = (~IO0PIN >> 4) & 0x0f;
cout << pos(8,2)<< sk << ;
}
Elektronika Praktyczna 11/2006
103
Wyszukiwarka
Podobne podstrony:
Mikrokontrolery ARM cz1
Mikrokontrolery ARM cz10
Mikrokontrolery ARM cz14
Mikrokontrolery ARM cz8
Mikrokontrolery ARM cz15
Mikrokontrolery ARM cz21
Mikrokontrolery ARM cz19
Mikrokontrolery ARM cz3
Mikrokontrolery ARM cz6
Mikrokontrolery ARM cz22
Mikrokontrolery ARM cz18
Mikrokontrolery ARM cz18
Mikrokontrolery ARM cz11
Mikrokontrolery ARM cz13
Mikrokontrolery ARM cz17
Mikrokontrolery ARM cz5
Mikrokontrolery ARM cz20
Mikrokontrolery ARM cz7
Mikrokontrolery ARM cz9
więcej podobnych podstron