38
Programowanie
Elektronika dla Wszystkich
Kontynuujemy dzi rozwijanie naszej
zaawansowanej obs³ugi wywietlacza alfanu-
merycznego. Stworzymy interfejs umo¿liwia-
j¹cy obs³ugê wielu jêzyków jednoczenie.
Przy tej okazji poznamy tablicê wielowymia-
row¹ oraz typ wyliczeniowy. Poka¿ê, w jaki
sposób umieciæ w pamiêci programu tablicê
wskazuj¹ca na napisy w pamiêci programu
ta konstrukcja wbrew pozorom nie jest tak
bardzo oczywista.
Projekt multilang
Dzisiejszy program bêdzie korzysta³ z wiêk-
szoci plików, które zosta³y utworzone w po-
przedniej czêci. Utwórz nowy katalog i sko-
piuj do niego pliki lcd.h, lcd.c, makra.h oraz
harddef.h z czêci 9. Mo¿esz skopiowaæ tak¿e
dotychczasowy makefile, jednak z plików
ród³owych usuwamy local.c, a polu TAR-
GET przypisujemy multilang (nazwê nowego
projektu).
W katalogu projektu utwórz nowy folder
o nazwie lang. Umiecimy w nim pliki defi-
niuj¹ce wszystkie nasze jêzyki. Proponujê
dodanie utworzonego katalogu do projektu
Programmers Notepada jako Magic Folder.
Folder taki odwzorowuje rzeczywist¹ zawar-
toæ katalogu. Klikamy prawym klawiszem na
ikonie projektu w oknie Projects i z pojawia-
j¹cego siê menu wybieramy Add Magic
Folder. Uruchomiony zostanie kreator, który
PP
PP
rr
rr
oo
oo
gg
gg
rr
rr
aa
aa
m
m
m
m
oo
oo
w
w
w
w
aa
aa
nn
nn
ii
ii
ee
ee
pp
pp
rr
rr
oo
oo
cc
cc
ee
ee
ss
ss
oo
oo
rr
rr
óó
óó
w
w
w
w
w
w
w
w
jj
jj
êê
êê
zz
zz
yy
yy
kk
kk
uu
uu
CC
CC
czêæ 10
Idea rozwi¹zania.
Obs³uga wielu jêzyków
Myl¹ przewodni¹ tworzonego dzi wielojêzycznego
interfejsu jest oddzielenie wszystkich napisów od
reszty aplikacji. Interfejs powinien byæ napisany
w taki sposób, aby wybór jêzyka nastêpowa³ tylko
w jednym miejscu (na przyk³ad odpowiednia pozycja
menu) i tylko w tym miejscu musimy zainteresowaæ
siê tym, ile jêzyków zosta³o zainstalo-
wanych, jakie to s¹ jêzyki i jakie s¹ ich
nazwy. Ca³a reszta programu powinna
dzia³aæ niezale¿nie od wybranego jêzy-
ka i nie bêdzie nawet wiedzia³a, jaki
jêzyk zosta³ wybrany.
Ideê rozwi¹zania pokazuje rysunek w
ramce. Dla czytelnoci nie umieci³em
tutaj tablicy zawieraj¹cej informacje o
znakach specjalnych. Napisy nadal
umieszczamy w pamiêci programu.
Jednak teraz, w stosunku do prostej
wersji jednojêzycznej, stworzymy tab-
licê dwuwymiarow¹, która bêdzie
zawieraæ wskaniki na wszystkie ³añcu-
chy. Takie rozwi¹zanie zajmuje dodat-
kowo po 2 bajty na ka¿dy napis (szes-
nastobitowy wskanik). Z jednej strony
zwiêksza to zajêtoæ pamiêci, jednak z drugiej umo¿-
liwia szybkie wyszukanie interesuj¹cego nas tekstu.
Nie mo¿emy umieszczaæ tekstów bezporednio w tab-
licy, poniewa¿ ka¿dy napis mo¿e mieæ inn¹ d³ugoæ.
Funkcje korzystaj¹ce z napisów bêd¹ pos³ugiwaæ
siê ich identyfikatorem, czyli indeksem w tablicy.
Naszym zadaniem bêdzie tak¿e takie zdefiniowanie
identyfikatorów, aby ich numeracja by³a tworzona
automatycznie.
ABC... C
Tablice wielowymiarowe
W jêzyku C tablice wielowymiarowe s¹ tworzone
w pewien specyficzny sposób. Od strony logicznej
taka tablica jest tablic¹ sk³adaj¹c¹ siê z tablic. Wi¹¿e
siê z tym okrelony sposób tworzenia oraz obs³ugi.
Przyk³ad utworzenia dwuwymiarowej tablicy:
cchhaarr
tablica[[
2
]][[
3
]];;
Do poszczególnych elementów dostajemy siê
nastêpuj¹co:
tablica[[a]][[b]] ==
0
;;
Nie tak jak ma to miejsce na przyk³ad w Paskalu:
tablica[[a,, b]] ==
0
;;
//le!
Fizycznie, w pamiêci, elementy roz³o¿one s¹ tak
jak pokazuje rysunek w ramce. Ta wiedza pozwala
nam obliczyæ pozycjê w pamiêci danego elementu.
Jest to wa¿ne, jeli chcemy dostaæ siê do tablicy za
pomoc¹ wskanika. Dla naszej tablicy:
Przesuniêcie = a*3 + b
Tablicê do funkcji mo¿emy przekazaæ na dwa
sposoby. Pierwszy z nich to poprzez wskanik:
vvooiidd
ZerujP((
cchhaarr
** pt,, uint16_t ilosc))
{
ffoorr
((;; ilosc>>
0
;; ilosc))
**((pt++++)) ==
0
;;
}}
Przyk³ad wywo³ania takiej funkcji dla naszej tablicy:
ZerujP((((
cchhaarr
**))tablica,,
6
));;
Drugi sposób wymaga jawnego podania rozmiaru
tablicy:
vvooiidd
Zeruj((
cchhaarr
t[[
2
]][[
3
]]))
{
uint16_t a,,b;;
ffoorr
((a==
0
;; a<<
2
;; a++++))
ffoorr
((b==
0
;; b<<
3
;; b++++))
t[[a]][[b]] ==
0
;;
}}
Zauwa¿, ¿e aby obliczyæ pozycjê elementu w pa-
miêci, kompilator nie potrzebuje informacji o rozmia-
rze najwy¿szego wymiaru (2). Rzeczywicie okazuje
siê, ¿e mo¿na go pomin¹æ przy podawaniu listy argu-
mentów funkcji:
vvooiidd
Zeruj((
cchhaarr
t[[]][[
3
]]))......
Wywo³anie naszej funkcji zeruj¹cej przebiega³oby
teraz nastêpuj¹co:
Zeruj((tablica));;
Co bardzo wa¿ne, do funkcji zostanie przes³any
jedynie wskanik nie kopia ca³ej tablicy.
Aby zainicjowaæ wstêpnie tablicê wielowymiaro-
w¹, poszczególne wiersze grupujemy za pomoc¹
nawiasów klamrowych. Powstaje doæ intuicyjny zapis:
cchhaarr
tablica[[
2
]][[
3
]] ==
{
{
a
,,
b
,,
c
},,
{
A
,,
B
,,
C
}
};;
Ponownie naj³atwiej powy¿szy zapis zrozumieæ,
odnosz¹c siê do naszego stwierdzenia, ¿e mamy
do czynienia z tablic¹ tablic. Tablica dwuelementowa
zawiera dane inicjuj¹ce ka¿d¹ z jej sk³adowych, tróje-
lementowych tablic. Rozumuj¹c w identyczny sposób,
mo¿na inicjowaæ tablice o dowolnej liczbie wymiarów.
Na koniec warto wspomnieæ o pewnym drobiazgu.
Korzystalimy ju¿ z tablic, których rozmiar by³ wyli-
czany automatycznie przez kompilator. W przypadku
tablic wielowymiarowych automatycznie obliczony
mo¿e byæ jedynie wymiar najwy¿szy:
cchhaarr
tablica[[]][[]] ==
//le
((......))
cchhaarr
tablica[[]][[
3
]] ==
//Dobrze
((......))
Przyk³ad: listing 97
zapyta nas o wskazanie folderu, który zosta-
nie odwzorowany. W nastêpnym kroku zosta-
niemy poproszeni o podanie typu plików,
jakie maj¹ byæ brane pod uwagê wpisujemy
*.h. Reszt¹ opcji mo¿emy siê nie przejmowaæ
i zostawiæ ich wartoci domylne.
Stwórzmy od razu pliki jêzyków. Bêd¹ to
doæ nietypowe pliki nag³ówkowe, poniewa¿
bêdziemy tworzyæ w nich zmienne napisowe.
Wa¿ne jest, aby nag³ówki te do³¹czaæ tylko
w jednym miejscu ca³ego programu. Z tego
powodu te¿ mo¿emy pomin¹æ pisanie sek-
wencji #ifndef #endif.
Plik lang/polski.h powsta³ poprzez skopio-
wanie zawartoci pliku local.c z poprzedniej
czêci. Zmienimy jedynie tworzone napisy
a nazwy zmiennych zmodyfikujemy tak, aby
zaczyna³y siê od liter PL_. Pokazuje to listing
94. Plik jêzyka angielskiego bêdzie jeszcze
prostszy, poniewa¿ jêzyk ten nie wymaga
definicji znaków specjalnych zobacz
listing 95.
Zarz¹dzanie jêzykami
Stwórzmy teraz modu³ odpowiedzialny za
zarz¹dzanie jêzykami. Utwórz dwa nowe
pliki: langsys.c oraz langsys.h i do-
daj je do projektu. Przypominam
o wpisaniu pliku langsys.c do pliku
makefile.
Listing 96 pokazuje niezbêdne
nam nag³ówki. Zauwa¿, ¿e nie do³¹-
czamy tutaj pliku <avr/io.h>. Modu³
langsys nie zajmuje siê sprzêtem, dla-
tego te¿ plik ten nie jest potrzebny. Do
pliku langsys.c do³¹czamy pliki
wszystkich jêzyków i co bardzo wa¿ne
robimy to tylko tutaj.
Zaraz po do³¹czeniu nag³ówków
tworzymy wszystkie potrzebne zmien-
ne oraz tablice zawieraj¹ce dane
pokazuje to listing 97. Pojawia siê
w tym miejscu po raz pierwszy tablica
wielowymiarowa, o której mo¿esz
przeczytaæ w odpowiedniej ramce.
Zwracam Twoja uwagê na wybrane
przypisanie wymiarów: wy-
miar najwy¿szy to wymiar
jêzyka, wymiar ni¿szy to
kolejne teksty. Gdybymy
wybrali odwrotn¹ konwen-
cjê, rozmiar tablicy móg³by
dostosowywaæ siê automa-
tycznie do iloci napisów.
Jednak inicjowanie takiej tablicy wymaga³o-
by wiêcej pisania: w naszym przypadku
nawiasy klamrowe obejmuj¹ wszystkie teksty
danego jêzyka; w przypadku odwrócenia
indeksów, obok siebie pisalibymy te same
napisy dla ró¿nych jêzyków i konieczne by³o-
by ka¿dorazowe ich objêcie klamrami. Rze-
czywicie dla tabeli 2x2 nie widaæ ró¿nicy,
jednak zwykle w programie ró¿nych tekstów
39
Programowanie
Elektronika dla Wszystkich
ABC... C
Tablica wskaników
w pamiêci programu
na napisy w pamiêci programu
Dzi mamy zamiar korzystaæ z tablicy zawieraj¹cej
wskaniki na wszystkie wykorzystywane napisy.
ANSI-C daje nam proste narzêdzie do tworzenia
takich tablic:
cchhaarr
** strTab[[]] ==
{
Napis 1
,,
Napis 2
,,
}};;
Kompilator umieszcza w pamiêci napisy, a w tab-
licy wskaniki na nie. Pytanie jednak: jaka to bêdzie
pamiêæ? Mamy do czynienia z problemem, na który
natknêlimy siê kilkukrotnie ANSI-C zak³ada jed-
nolit¹ przestrzeñ adresow¹. W naszym przypadku
dane zostan¹ umieszczone w pamiêci RAM. Trafi tam
zarówno tablica wskaników, jak i same ³añcuchy.
Niewiele pomo¿e poni¿sza sztuczka:
prog_char** strTab[[]] PROGMEM ==
((......))
Tablica co prawda znajdzie siê w pamiêci progra-
mu, jednak napisy umieszczone zostan¹ w pamiêci
danych. Pomijaj¹c wszystkie mo¿liwoci które nie
dzia³aj¹ (plik z propozycj¹ eksperymentów znajdzie
siê w materia³ach dodatkowych), przedstawiam
rozwi¹zanie przyjmowane przez AVR-GCC:
prog_char napis1[[]]==
Napis 1
;;
prog_char napis2[[]]==
Napis 2
;;
ccoonnsstt
prog_char** strTab[[]]
PROGMEM ==
{
napis1,, napis2
};;
Sztuczka polega na utworzeniu oddzielnie napi-
sów w pamiêci programu, a nastêpnie wprowadzenie
do tablicy wskaników na odpowiednie zmienne.
Dziêki operatorowi const pozbêdziemy siê ostrze-
¿enia podczas kompilacji, mówi¹cego, ¿e inicjujemy
tabelê niekompatybilnymi elementami. Elementy
w pamiêci programu domylnie s¹ typu const. Opis,
co to oznacza, znajduje siê w oddzielnej ramce.
Przyk³ad: listing 97
Listing 95 plik lang/english.h
// Teksty
prog_char EN_strDisplay[[]] ==
English
;;
prog_char EN_strStart[[]] ==
Welcome in
English Version
;;
Listing 98 funkcje manipuluj¹ce jêzykiem w local.c
// Iloæ zainstalowanych jêzyków
iinnlliinnee
uint8_t langsys_GetNumOfLangs((
vvooiidd
))
{{
rreettuurrnn
ELEMS((langsys_strTable));;
}}
// Wybór jêzyka
iinnlliinnee vvooiidd
langsys_Select((uint8_t index))
{{
langsys_sel == index;;
}}
// Pobranie informacji o wybranym jêzyku
iinnlliinnee
uint8_t langsys_GetSelected((
vvooiidd
))
{{
rreettuurrnn
langsys_sel;;
}}
// Pobranie nazwy jêzyka o podanym indeksie
prog_char** langsys_GetLangName((uint8_t index))
{{
iiff
((index >> ELEMS((langsys_strTable))))
rreettuurrnn
NULL;;
rreettuurrnn
((prog_char**))pgm_read_word_near((
&&langsys_strTable[[index]][[IDS_LANGNAME]]));;
}}
Listing 96 nag³ówki pliku langsys.c
#include <inttypes.h>
#include <avr/pgmspace.h>
#include makra.h
#include lcd.h
#include langsys.h
// Tutaj nag³ówki jêzyków
#include lang/english.h
#include lang/polski.h
Listing 99 funkcje podaj¹ce dane dla wybranego jêzyka (langsys.c)
// Pobieranie wskanika na informacjê o znakach specjalnych
LCD_LOCAL_PGM** langsys_GetSpec((
vvooiidd
))
{{
rreettuurrnn
((LCD_LOCAL_PGM**))pgm_read_word_near((&&langsys_lcdspec[[langsys_sel]]));;
}}
// Pobieranie wybranego napisu
prog_char** langsys_GetText((uint8_t index))
{{
rreettuurrnn
((prog_char**))pgm_read_word_near((&&langsys_strTable[[langsys_sel]][[index]]));;
}}
Listing 97 zmienne i tablice w local.c
ssttaattiicc
uint8_t langsys_sel ==
0
;;
// wybrany jêzyk
// Definicja informacji o znakach specjalnych
ccoonnsstt
LCD_LOCAL_PGM** langsys_lcdspec[[]] ==
{
NULL,,
PL_lcdspec
};;
// Tablica wszystkich posiadanych napisów
// [jêzyk][tekst]
ccoonnsstt
prog_char** langsys_strTable[[
2
]][[
2
]] PROGMEM ==
{
{
EN_strDisplay,,
EN_strStart
},,
{
PL_strDisplay,,
PL_strStart
}
};;
Listing 94 plik lang/polski.h
LCD_LOCAL_PGM PL_lcdspec[[
18
]] ==
{
((......))
}};;
// Teksty
prog_char PL_strDisplay[[]] ==
Polski
;;
prog_char PL_strStart[[]] ==
Witaj w wersji Polskiej
;;
40
Programowanie
Elektronika dla Wszystkich
ABC... C
Typ wyliczeniowy enum
Przyk³adowa sk³adnia przy tworzeniu typu wylicze-
niowego jest nastêpuj¹ca:
eennuumm
DniTygodnia
{
PONIEDZIALEK,, WTOREK,,
SRODA,, CZWARTEK,, PIATEK,,
SOBOTA==
0x10
,, NIEDZIELA
}dt;;
W tym przypadku tworzymy nowe wyliczenie
o nazwie DniTygodnia oraz tworzymy now¹ zmienn¹
o nazwie dt. Jeli pominiemy tworzenie zmiennej,
mo¿emy zrobiæ to póniej:
eennuumm
DniTygodnia dt;;
Zmienna typu wyliczeniowego to najmniejsza
zmienna typu ca³kowitego, która jest w stanie pomie-
ciæ wszystkie wyliczone wartoci.
Nazwy wystêpuj¹ce w poszczególnych wylicze-
niach musz¹ byæ identyfikatorami w rozumieniu C,
a wiêc nie mog¹ zawieraæ znaków specjalnych i nie
mog¹ zaczynaæ siê od cyfry.
W ró¿nych wyliczeniach nazwy nie mog¹ siê po-
wtarzaæ. Przypisanie identyfikatora do liczby ma taki
sam zasiêg, jakby by³o tworzone za pomoc¹ #define.
Jeli w wyliczeniu nie podamy jawnie ¿adnych
wartoci, zostan¹ im przypisane kolejne liczby ca³ko-
wite poczynaj¹c od 0. Jeli natomiast w którym
momencie wartoæ zostanie podana, nastêpny, niepo-
dany jawnie identyfikator otrzymuje wartoæ o 1
wiêksz¹.
Wartoci w wyliczeniu mog¹ siê powtarzaæ:
eennuumm
LICZBY
{
JEDEN==
1
,,
JEDYNKA==
1
,,
PIERWSZY==
1
};;
Tworz¹c typ wyliczeniowy, nie musimy ani tworzyæ
zmiennej, ani umo¿liwiaæ jej przysz³ego utworzenia:
eennuumm
{ bn1,, bn2 };;
W powy¿szym przypadku jedynie identyfikato-
rom bn1 oraz bn2 przyporz¹dkowywane s¹ wartoci
0 oraz 1.
Przewag¹ takiego zapisu w stosunku do wyko-
rzystania s³owa #define jest automatyczne generowa-
nie kolejnych wartoci przez kompilator.
Uwaga: AVR-GCC nie sprawdza, czy do zmiennej
wyliczeniowej wpisywana jest odpowiednia wartoæ
(z zakresu wyliczonego w klamrach). Mo¿na powie-
dzieæ, ¿e odpowiednia zmienna oraz identyfikatory
tworzone s¹ oddzielnie.
Dodatkow¹ zalet¹ zmiennych wyliczeniowych
jest to, ¿e AVRStudio pokazuje wartoci takiej zmien-
nej symbolicznie.
Przyk³ad: listing 100
ABC... C
Kwalifikator typu const
Kwalifikator const (sta³y) zastosowany podczas dek-
laracji zmiennej mówi nam, ¿e jej wartoæ nie bêdzie
nigdy zmieniana. Kompilator zg³osi b³¹d, jeli
bêdziemy próbowali zmieniæ jej wartoæ.
Oznaczenie tablicy kwalifikatorem const ozna-
cza, ¿e ¿aden z jej elementów nie bêdzie modyfiko-
wany.
S³owo const u¿yte do wskanika w licie para-
metrów funkcji oznacza, ¿e funkcja nie bêdzie
modyfikowaæ zmiennej przez niego wskazywanej.
Czasami jest to wa¿ne, poniewa¿ chcemy mieæ pew-
noæ, ¿e tak jest w istocie. Pomaga to tak¿e kompila-
torowi w optymalizacji kodu.
Uwaga: jeli zmienna oznaczona jako const
fizycznie znajduje siê w pamiêci RAM, tak napraw-
dê mo¿e byæ zmieniona. Mo¿na to uczyniæ na przy-
k³ad poprzez wskanik oraz jego rzutowanie na typ
bez kwalifikatora const:
ccoonnsstt iinntt
wartosc ==
100
;;
((......))
**((((
iinntt
**))&&wartosc)) ==
1
;;
O ile w AVR-GCC to dzia³a, jednak rzeczywisty
efekt takiego zagrania nie jest okrelony przez normê
ANSI. Takich sztuczek nie powinno siê stosowaæ
w za³o¿eniu oznaczenie, ¿e zmienna nie powinna
byæ modyfikowana, ma swój sens.
Przyk³ad: listing 97
Ci¹g dalszy ze strony 25.
W sk³ad podstaw wchodz¹ równie¿ zagad-
nienia zwi¹zane z uk³adami analogowymi.
Prezentowane s¹ podstawowe uk³ady elektro-
niczne, takie jak wzmacniacze, generatory,
scalone uk³ady analogowe, zasilacze, stabili-
zatory, przetworniki A/C oraz C/A, modulato-
ry i demodulatory. Wymienione zagadnienia
omawiane s¹ od strony zastosowañ, zasady
dzia³ania oraz zasad projektowania. T³uma-
czone s¹ równie¿ zagadnienia zwi¹zane z tech-
nik¹ cyfrow¹ z uwzglêdnieniem budowy, dzia-
³ania oraz zastosowania uk³adów cyfrowych
(materia³ obejmuje bramki, liczniki, rejestry,
uk³ady GAL, pamiêci pó³przewodnikowe itd.).
Urz¹dzenia elektroniczne to drugi stopieñ
wtajemniczenia, na lekcjach zwi¹zanych
z tym blokiem przedmiotowym przedstawia-
ne s¹ zasady dzia³ania i obs³ugi: urz¹dzeñ
elektroakustycznych, odbiorników radiowych
i telewizyjnych, urz¹dzeñ s³u¿¹cych do
odczytu i zapisu informacji, telewizji kablo-
wej i satelitarnej itd. Ale to nie wszystko.
Uczniowie w ramach zajêæ poznaj¹ równie¿
budowê i zasadê dzia³ania systemów pomia-
rowych oraz nowoczesnych przyrz¹dów
pomiarowych. Uzupe³nienie wiedzy stanowi¹
zagadnienia zwi¹zane z urz¹dzeniami auto-
matyki (uk³ady wykonawcze, sygnalizatory,
regulatory, czujniki itp.)
Pomiary elektroniczne stanowi¹ najwa¿-
niejszy element kszta³cenia elektronika,
na tych zajêciach nastêpuje prze³o¿enie wie-
dzy teoretycznej na praktyczne umiejêtnoci.
W sk³ad pomiarów wchodz¹ cztery laboratoria
(elektryczne, elektroniki analogowej i cyfro-
wej, uk³adów mikroprocesorowych, urz¹dzeñ
elektronicznych) poznawane w kolejnych
latach nauki. Zajêcia obejmuj¹ projektowanie
i badanie elementów, uk³adów i urz¹dzeñ
elektronicznych. Wyposa¿enie obejmuje
m.in. zasilacze napiêcia sta³ego, generatory
funkcyjne, mierniki analogowe i cyfrowe,
mostki RLC, dydaktyczne systemy mikropro-
cesorowe oraz ca³¹ masê modeli, makiet oraz
elementów przeznaczonych do diagnozowa-
nia i badania. Obecnie standardem staje siê
równie¿ komputer pomagaj¹cy w interpretacji
otrzymanych wyników pomiarowych. Do-
k³adny wykaz wszystkich nauczanych zagad-
nieñ, umiejêtnoci oraz wymaganego wyposa-
¿enia dostêpny jest na stronie MENiS
(www.menis.gov.pl/ksztzaw/strategia/strateg-
ia.php
)
. W technikum mamy równie¿ prakty-
kê zawodow¹, ale na ten temat by³a ju¿ mowa.
Przedmiotów zawodowych, jakie obo-
wi¹zuj¹ ucznia technikum, jest sporo, jedne
³atwiejsze, drugie trudniejsze. Czêæ osób
preferuje przedmioty teoretyczne, czêæ prak-
tyczne, wszystko jest kwesti¹ indywidualnych
predyspozycji. Czy mo¿na te wszystkie
zagadnienia i umiejêtnoci mo¿na opanowaæ
samodzielnie w zaciszu domowym w rozs¹d-
nym czasie? Odpowied na to pytanie pozo-
stawiam pod os¹d czytelnika.
Co wybraæ?
Nie by³o moim zamiarem wbrew pozorom
gloryfikowaæ technikum elektronicznego, sta-
ra³em siê jedynie w sposób doæ obiektywny
przedstawiæ jego cele, zadania i umiejêtnoci,
jakie przekazuje swoim absolwentom. Wybór,
jak¹ drogê rozwoju zawodowego wybraæ,
przygotowuj¹c siê do studiowania elektronik
poprzez technikum czy liceum, musi nale¿eæ
do samych zainteresowanych w tym
momencie absolwentów gimnazjum.
Na zakoñczenie ju¿ ca³kiem subiektyw-
nie. Swoj¹ edukacjê techniczn¹ rozpocz¹³em
w technikum elektrycznym, kontynuuj¹c j¹ na
studiach technicznych (kierunek elektronika
i telekomunikacja). Przedmioty zawodowe
z technikum bardzo siê przyda³y i brak³o do
pe³ni szczêcia niestety kilku z zakresu elek-
troniki. Z matematyk¹ by³o trudno, ale mo¿na
siê by³o nauczyæ, natomiast bardzo du¿o
absolwentów liceum odpad³o na przedmio-
tach zawodowych, choæ byli i tacy, którzy
radzili sobie doskonale. Obecnie prowadzê
zajêcia m.in. w technikum elektronicznym
i uwa¿am, ¿e pomimo ca³ej masy problemów
zwi¹zanych z podrêcznikami, wyposa¿eniem
oraz ogromem ciê¿kiej pracy, jak¹ musi siê
w³o¿yæ w naukê, oraz trudnego egzaminu
zawodowego jest to szko³a warta polecenia.
Szko³y techniczne maj¹ w sobie jeszcze to
co, bli¿ej nieokrelone, ale ucz¹ce wspó³-
pracy i zaufania do kolegów, posiadaj¹ swois-
ty klimat niedostêpny w innych szko³ach.
Z perspektywy czasu i pomimo ci¹gle istnie-
j¹cych niedoskona³oci jeszcze raz polecam
technikum jako w³aciwy wybór dla osób
zajmuj¹cych siê elektronik¹.
Piotr Brzózka
pbrzozka@elportal.pl
jest wiêcej ni¿ jêzyków. Problemem automa-
tycznej inicjacji rozmiaru tablicy jeszcze siê
zajmiemy.
Zgodnie z ramk¹ o idei naszego programu,
utworzymy teraz funkcje manipuluj¹ce jêzy-
kiem. Spójrz na listing 98. Zwróæ szczególn¹
uwagê na sposób odczytu danych z naszej tab-
licy w funkcji langsys_GetLangName. Dostêp
wygl¹da doæ skomplikowanie niestety nie
mo¿emy czytaæ bezporednio danych z tabli-
cy umieszczonej w pamiêci programu. Makro
pgm_read_word_near odczytuje szesnastobi-
tow¹ liczbê z pamiêci programu. Poniewa¿
my wiemy, ¿e w tym miejscu znajduje siê
wskanik i wskanika oczekujemy, musimy
wykonaæ rzutowanie, aby odczytana wartoæ
mog³a byæ prawid³owo wykorzystana. Ko-
rzystaj¹c z makra pgm_read_word_near
zak³adamy jednoczenie, ¿e nasze wskaniki
nie maj¹ wiêcej ni¿ 16 bitów zwykle bêdzie
to prawd¹. Nale¿a³oby jedynie zweryfikowaæ
poprawnoæ tego za³o¿enia w przypadku wy-
korzystania procesora o pamiêci programu
wiêkszej ni¿ 64KB przy wykorzystaniu bar-
dzo du¿ej iloci sta³ych.
Ostatnie dwie funkcje modu³u langsys
pokazuje listing 99. Zwracaj¹ one wskanik
na znaki specjalne (jeli w danym jêzyku
znaki specjalne nie wystêpuj¹, zwracana jest
wartoæ NULL) oraz tekst o podanym identy-
fikatorze.
W tym miejscu plik ród³owy mamy
zakoñczony. Pozostaje nam jeszcze stworze-
nie pliku nag³ówkowego widocznego na lis-
tingu 100. Umieszczamy w nim deklaracje
funkcji oraz symboliczne oznaczenia identy-
fikatorów. Identyfikatory moglibymy two-
rzyæ za pomoc¹ poznanych do tej pory
dyrektyw #define. Jednak dla wiêkszej ilo-
ci napisów dzia³anie takie jest doæ uci¹¿li-
we i ³atwo o pomy³kê. Zamiast tego wyko-
rzystamy dzi typ wyliczeniowy (ramka).
Dziêki temu, jeli tylko zachowamy kolej-
noæ identyfikatorów zgodn¹ z kolejnoci¹
zapisania wskaników w tablicy, identyfika-
tory zostan¹ prawid³owo wygenerowane
automatycznie. Przy identyfikatorach napi-
sów przyjmijmy zasadê, aby zaczynaæ je
zawsze du¿ymi literami IDS_ a reszta
nazwy zgodna by³a z nazw¹ zmiennej ³añcu-
chowej bez liter EN/PL_str.
U³atwi to orientacjê w ko-
dzie oraz tworzenie wyli-
czenia wystarczy skopio-
waæ dane inicjuj¹ce jeden
jêzyk w tablicy langsys_str
Table i dokonaæ zmian
komend¹ Edit->Replace.
Wyj¹tkiem od tej regu³y s¹
identyfikatory o specjalnym
znaczeniu, pisane w ca³oci
du¿ymi literami (aktualnie
jedynie IDS_LANGNAME).
Dostosowanie modu³u lcd
Modu³ lcd, z którego chcemy korzystaæ,
zak³ada³ wystêpowanie w programie modu³u
local i korzysta³ z umieszczonej w nim infor-
macji o znakach specjalnych. W stosunku do
tej sytuacji pojawiaj¹ siê aktualnie trzy ró¿nice:
1. Nie ma modu³u local.
2. Nie ma mo¿liwoci bezporedniego dostê-
pu do tablicy z danymi lokalnymi nale¿y
korzystaæ z funkcji langsys_GetSpec.
3. Istnieje mo¿liwoæ, ¿e tablica danych lokal-
nych dla danego jêzyka nie istnieje nale¿y
zabezpieczyæ siê przed prób¹ czytania spod
adresu NULL.
Otwórz poprzednio napisany plik lcd.c
i korzystaj¹c z komendy Edit->Find, znajd
wszystkie wyst¹pienia s³owa local_. Zauwa-
¿ysz, ¿e ze wspomnianego modu³u korzysta-
my jedynie w dwóch miejscach. Nie bêdzie-
my wiêc mieli du¿o pracy.
W pierwszej kolejnoci do³¹czenie #include
local.h zmieniamy na #include langsys.h.
Resztê koniecznych zmian mo¿esz zobaczyæ
na listingach 101 oraz 102. Dodane lub zmo-
dyfikowane fragmenty oznaczam kolorem
Test
Wszystkie modu³y s¹ ju¿ gotowe, pora na ich
po³¹czenie oraz napisanie
funkcji g³ównej programu.
Utwórz plik main.c. Jeli
korzystasz z makefile z po-
przedniej czêci, powinien on
byæ ju¿ tam dodany. Propo-
zycjê prostego testu przedsta-
wia listing 103. Funkcja
obs³ugi przycisków zosta³a
45
Programowanie
Elektronika dla Wszystkich
ABC... C
Funkcja traktowana jak zmienna
Rozmawialimy ju¿ o tym, jak wygodn¹ w³asnoci¹ C
jest mo¿liwoæ wykonywania z³o¿onych obliczeñ
w jednej linii. Do wygody tej przyczynia siê tak¿e
mo¿liwoæ traktowania jako zmiennej ka¿dej funkcji,
która zwraca jak¹ wartoæ. Co zrozumia³e, bêdzie to
zmienna tylko do odczytu. Kompilator zawsze zadba
o to, aby potrzebne funkcje zosta³y wywo³ane we
w³aciwym momencie, a zwracana przez nie wartoæ
wziêta do obliczeñ.
O ile sprawa wydaje siê oczywista w przypadku
prostych operacji arytmetycznych, zauwa¿, ¿e daje to
znacznie szersze mo¿liwoci. Znakomitym przyk³a-
dem s¹ listingi 101 oraz 102. W tym miejscu funkcja
langsys_GetSpec zwraca wskanik, który natychmiast
jest obs³ugiwany jako tablica. Mo¿na by skorzystaæ
ze zmiennej pomocniczej, do której zosta³aby zapisa-
na wartoæ zwracana przez funkcje, a nastêpnie dosta-
walibymy siê do tablicy, tak jak robilimy to do tej
pory, jednak nie ma potrzeby rozbijania przedstawio-
nego dzia³ania.
Uwa¿aj: Standard nie definiuje w takim przypad-
ku, w jakiej kolejnoci funkcje zostan¹ wywo³ane.
Mo¿e okazaæ siê nawet, ¿e pewne funkcje nie zostan¹
w ogóle wywo³ane, jeli kompilator stwierdzi, ¿e ich
wartoæ nie ma znaczenia dla wyniku obliczeñ. Mog¹
one byæ wywo³ywane zgodnie z kolejnoci¹, w jakiej
wykonywane s¹ dzia³ania, ale mog¹ tak¿e byæ wywo-
³ane przed rozpoczêciem jakichkolwiek obliczeñ. Jeli
zale¿y Ci na kolejnoci wywo³ania funkcji, nie obej-
dzie siê bez zmiennych pomocniczych konieczne
jest wtedy rêczne wywo³anie funkcji oraz zapisanie
ich wyników.
Przyk³ady: listingi 101 oraz 102
Listing 101 modyfikacja funkcji w lcd_GetSpec
ssttaattiicc
uint8_t lcd_GetSpec((uint8_t s_index))
{{
// Zabezpieczenie
iiff
((langsys_GetSpec(()) ==== NULL))
rreettuurrnn
0x20
;;
((......))
// Nic nie znaleziono
rreettuurrnn
pgm_read_byte((&&((langsys_GetSpec(())[[s_index]]..cAlt))));;
}}
Listing 100 plik langsys.h
#ifndef LANGSYS_H_INCLUDED
#define LANGSYS_H_INCLUDED
//_____________________________________________
// Funkcje interfejsu
iinnlliinnee
uint8_t langsys_GetNumOfLangs((
vvooiidd
));;
iinnlliinnee vvooiidd
langsys_Select((uint8_t index));;
iinnlliinnee
uint8_t langsys_GetSelected((
vvooiidd
));;
prog_char** langsys_GetLangName((uint8_t index));;
LCD_LOCAL_PGM** langsys_GetSpec((
vvooiidd
));;
prog_char** langsys_GetText((uint8_t index));;
//_____________________________________________
// Indeksy dla poszczególnych napisów
eennuumm
{
IDS_LANGNAME,,
IDS_Start
};;
#endif
//LANGSYS_H_INCLUDED
Listing 102 modyfikacja funkcji w lcd_UpdateCGRAM
vvooiidd
lcd_UpdateCGRAM((
vvooiidd
))
{{
// Zabezpieczenie
iiff
((langsys_GetSpec(()) ==== NULL))
rreettuurrnn
;;
((......))
ffoorr
((a==
0
;; a<<ELEMS((lcd_spec));; a++++))
{
// 0xff oznacza koniec danych
iiff
((lcd_spec[[a]] ====
0xff
))
bbrreeaakk
;;
// Wskanik na pocz¹tek danych wygl¹du znaku
uint8_t** pdata == langsys_GetSpec(())[[lcd_spec[[a]]]]..matrix;;
((......))
}
}
umieszczona bezporednio tutaj plik ten jest
plikiem chwilowym, s³u¿¹cym nam tylko
do testowania. W przysz³oci przyciski bêd¹
obs³ugiwanie inaczej.
Zauwa¿ sposób dostêpu do napisów, gdy
jêzyk zosta³ ju¿ wybrany. Niezale¿nie od
dokonanego wyboru przywitanie wywietlane
jest w taki sam sposób. W tym prostym pro-
gramie nie widaæ jeszcze si³y tkwi¹cej w roz-
wi¹zaniu napisów jest za ma³o, mamy jed-
nak pewnoæ, ¿e nowy modu³ dzia³a.
U³atwienie tworzenia
tablicy wskaników
Przy okazji listingu 97 obieca³em, ¿e zajmie-
my siê problemem ustawiania rozmiaru tabli-
cy wskaników na napisy. O ile przy dwóch
ró¿nych tekstach nie ma problemu z rêcznym
podaniem ich iloci, to przy rozwoju progra-
mu mo¿e to staæ siê doæ uci¹¿liwe. Kompila-
tor nie obliczy dla nas rozmiaru innego ni¿
najwy¿szy wymiar tablicy. Mo¿emy jednak
wykorzystaæ fakt, ¿e wyliczamy indeksy
kolejnych wpisanych wartoci. Niezbêdny
rozmiar tablicy to ostatni indeks + 1. Wpro-
wadmy dodatkowy identyfikator do wylicze-
nia zgodnie z listingiem 104. Umówmy siê
teraz, ¿e wszelkie nowe identyfikatory wpro-
wadzimy przed IDS_END. Identyfikator ten
jest równy wartoci ostatniego identyfikatora
³añcucha + 1. Mo¿emy teraz wykorzystaæ go
do ustawienia rozmiaru naszej tablicy, tak jak
zosta³o to pokazane na listingu 105.
Inne modyfikacje
Aktualnym ograniczeniem iloci napisów jest
wartoæ 256. Wynika to z przyjêtego typu
parametru funkcji langsys_GetTekst. Jest
ma³o prawdopodobne, aby pojawi³a siê
potrzeba wykorzystania wiêkszej iloci tek-
stów. Jeli jednak taka potrzeba zaistnieje,
zawsze mo¿na zmieniæ typ parametru na war-
toæ szesnastobitow¹ zostanie to prawid³o-
wo obs³u¿one. Wiêksze wartoci nie maj¹
sensu przy wykorzystaniu ca³ego dostêpne-
go zakresu mo¿emy ca³kowicie zape³niæ
pamiêæ procesora Atmega128 sam¹ tylko tab-
lic¹ wskaników dla jednego jêzyka.
Podsumowanie
Dzi utworzylimy kolejny element kojarzony
z zaawansowan¹ obs³ug¹ LCD. Wa¿ne jest jed-
nak to, ¿e modu³ ten jest bardzo uniwersalny
podaje nam jedynie ³añcuch
zawieraj¹cy napis w danym
jêzyku. Mo¿e on równie dobrze
wspó³dzia³aæ z wywietlaczem
alfanumerycznym, graficznym
czy nawet s³u¿yæ do wyboru jêzyka komu-
nikacji poprzez port RS232.
Poznajemy jednoczenie coraz bardziej
zaawansowane elementy jêzyka C. Tablice
wielowymiarowe czy mo¿liwoæ wykorzysta-
nia funkcji jak zmiennej jest czym co nie
pojawia³o siê w BASCOM-ie. Jeli teraz spra-
wia Ci to k³opot, pamiêtaj, ¿e C nie zmusza
Ciê do korzystania z tych opcji. Jeli chcesz,
mo¿esz korzystaæ ze zmiennych pomocni-
czych, a w pewnym momencie sam odkryjesz,
jak wygodnie jest zapisaæ algorytm bez nich.
W kolejnej czêci korzystaj¹c z dotychcza-
sowego dzie³a, stworzymy modu³ umo¿liwia-
j¹cy proste tworzenie przewijalnego menu
z zagnie¿d¿aniem opcji. Na systemie menu
skoñczymy zaawansowan¹ obs³ugê LCD
alfanumerycznego.
Rados³aw Koppel
radoslaw.koppel@elportal.pl
46
Programowanie
Elektronika dla Wszystkich
Listing 104 obliczenie iloci napisów
eennuumm
{
IDS_LANGNAME,,
IDS_Start,,
// KONIEC
IDS_END
};;
Listing 105 Wykorzystanie wyliczenia do obliczania rozmiaru tablicy
ccoonnsstt
prog_char** langsys_strTable[[
2
]][[IDS_END]] PROGMEM ==
((......))
Listing 103 plik main.c
#include <avr/io.h>
#include <stdio.h>
#include <util/delay.h>
#include harddef.h
#include makra.h
#include lcd.h
#include langsys.h
ssttaattiicc iinnlliinnee
uint8_t sw_wait((
vvooiidd
))
{{
ffoorr
((;;;;))
{
// Oczekiwanie na naciniêcie przycisku
iiff
((!!((PIN((SW_PORT)) &&
1
<<<<SW1))))
{
_delay_ms((
30
));;
iiff
((!!((PIN((SW_PORT)) &&
1
<<<<SW1))))
rreettuurrnn
1
;;
}
iiff
((!!((PIN((SW_PORT)) &&
1
<<<<SW2))))
{
_delay_ms((
30
));;
iiff
((!!((PIN((SW_PORT)) &&
1
<<<<SW2))))
rreettuurrnn
2
;;
}
}
}
iinntt
main((
vvooiidd
))
{{
// Inicjacja wyprowadzeñ
DDR((LCD_CTRLPORT)) == ((
1
<<<<LCD_E ||
1
<<<<LCD_RW ||
1
<<<<LCD_RS ||
1
<<<<LCD_LED));;
PORT((LCD_CTRLPORT)) == ~~((
1
<<<<LCD_E ||
1
<<<<LCD_LED));;
PORT((SW_PORT)) ==
1
<<<<SW1 ||
1
<<<<SW2;;
// Inicjacja wywietlacza
lcd_Init(());;
lcd_SetStatus((LCD_STATUS_DISP));;
// Wywietlenie zapytania o jêzyk
fputs_P((PSTR((
S1 -
)),, lcd_GetFile(())));;
fputs_P((langsys_GetLangName((
0
)),, lcd_GetFile(())));;
lcd_GoTo((
0
,,
1
));;
fputs_P((PSTR((
S2 -
)),, lcd_GetFile(())));;
fputs_P((langsys_GetLangName((
1
)),, lcd_GetFile(())));;
lcd_Update(());;
// Oczekiwanie na przycisk i wybranie jêzyka
langsys_Select((sw_wait(())--
1
));;
// Wywietlenie przywitania
lcd_Cls(());;
fputs_P((langsys_GetText((IDS_Start)),, lcd_GetFile(())));;
lcd_Update(());;
rreettuurrnn
0
;;
}}
Sterownik zegarowy i nie tylko...
- Urz¹dzenie, oprócz wbudowanego
zegara, posiada wiele ciekawych i po¿ytecznych funkcji. Oto niektóre z nich:
4 kana³y sterowane czasowo (godzina w³¹czenia, godzina wy³¹czenia),
3 kana³y sterowane rêcznie przez u¿ytkownika, 1 kana³ sterowany termicznie
(temperatura górna maksymalna, dolna minimalna z zakresu od -55 do +150
o
C),
cyfrowy czujnik temperatury i in.
Kompukser
- W popularnych kartach dwiêkowych
mamy do dyspozycji jedno gniazdo
wejciowe Line In typu miniJack.
Czasem jednak zachodzi potrzeba,
aby pod³¹czyæ do komputera wiêcej
sygna³ów audio. Opisany w artykule uk³ad rozwi¹zuje ten problem.
Ponadto umo¿liwia ³atw¹ rozbudowê o kolejne kana³y.
w n a s t ê p n y c h n u m e r a c h E d W
R E K L A M A