kursC_czesc018.pdf

(401 KB) Pobierz
215235220 UNPDF
Programowanie
P r o g r a m o w a n i e p r o c e s o r ó w
w j z y k u C
cz 18 – podsumowanie
Wykonywanie
dalekiego skoku
Do tej pory nauczylimy si wykorzystywa
instrukcje goto. Instrukcja ta ma jednak to do
siebie, e umoliwia skok tylko we wntrzu
funkcji. ANSI C posiada mechanizm, który
pozwala na wykonywanie skoków w obrbie
caego programu. Oferowany mechanizm jest
bardzo wygodny w szczególnych przypad-
kach.
Wyobramy sobie sytuacj, e wywouje-
my funkcj transmisji ramki danych, która
wielokrotnie wywouje funkcj transmisji baj-
tu jako dwóch bajtów ASCII, wywoujc
dwa razy funkcj transmisji jednego znaku.
Teraz pojawia si, problem: z jakiego powo-
du znak nie moe by wysany. W jaki sposób
wykryjemy powstanie bdu? Konieczne b-
dzie sprawdzanie wystpienia bdu w kolej-
nych funkcjach: Funkcja wysyania znaku
zwróci bd, wykryje to funkcja wysyania
bajtu w formacie ASCII i przekae do funkcji
transmisji ramki, która wreszcie zwróci bd
do funkcji wywoujcej.
Troszk to zamotane? Tak wanie powin-
no brzmie. Wszystko wyjani rysunek 72.
Opisana przed chwil droga to przejcie naj-
pierw po czarnych a póniej po czerwonych
strzakach. Zobacz jednak, e zaznaczyem
te du niebiesk strzak. Omija ona
wszystkie porednie sprawdzenia wy-
stpienia bdów. Jest to duo ele-
gantsze rozwizanie. Dodatkowo jest
bardziej efektywne, poniewa omija-
my cige sprawdzanie wartoci
zwracanych przez funkcje poredni-
czce w transmisji.
Moliwo wykonania takiego
skoku daj nam funkcje setjmp oraz
longjmp. Wyjanienie zasady ich
dziaania znajduje si w odpowied-
niej ramce. Jeli zawarte tam wyja-
nienie pozostawia jakie niejasnoci,
zobacz kod na listingu 238. Listing
ten, dla przejrzystoci pozbawiony
jest wielu elementów. Korzystamy
z naszej biblioteki obsugi LCD, któ-
r rozbudowalimy o funkcje pisania.
Funkcja DrawLine_P , przedstawiona
na listingu 239, ma za zadanie wypi-
sa informacj w linii i przesun we-
wntrzny „kursor” na lini nastpn.
Na listingach brakuje funkcji befo-
re_main, której zadaniem jest wcze-
nie pamici zewntrznej.
Spróbujmy razem przeanalizowa dziaa-
nie programu. Funkcja main dokonuje stan-
dardowej inicjacji. Wypisujemy na pierwszej
linii wywietlacza sówko „START” – wiemy
Listing 238 Przykad wykorzystania dalekiego skoku
void funkcja_2( void )
{
DrawLine_P(PSTR( „f2 - start“ ));
longjmp(err_jmp, 33 );
DrawLine_P(PSTR( „f2 - end“ ));
}
void funkcja_1( void )
{
DrawLine_P(PSTR( „f1 - start“ ));
funkcja_2();
DrawLine_P(PSTR( „f1 - end“ ));
}
// START
int main( void )
{
int ret ; // warto zwrócona przez setjmp
// Inicjacja wyprowadze, SPI oraz wywietlacza
(...)
Rys. 72 Propagacja bdów w
kolejnych wywoaniach funkcji
// Czyszczenie wywietlacza
lcd_Cls( 0x03 );
DrawLine_P(PSTR( „START“ ));
// Miejsce gdzie wróc wywoywane funkcje
ret = setjmp(err_jmp);
if (ret != 0 )
{
// Wypisuj informacj o bdzie
lcd_Printf( 0 , g_linia* 8 , PSTR( „Error: %d“ ),
0xfc , 0 ,
LCD_TM_FLASH | LCD_TM_TRANSPARENT , ret );
++g_linia;
lcd_Update();
} else
{
// setjmp wanie ustawio adres powrotu
funkcja_1();
}
ABC... C
setjmp i longjmp – jak to dziaa
DrawLine_P(PSTR( „END“ ));
powrót z funkcji zwizany jest z wykonaniem skoku, zwraca-
na warto równa jest wartoci podanej jako drugi parametr
funkcji longjmp.
Co wane, skok longjmp moe odbywa si tylko
wstecz. To znaczy, e ustawienia zapisane w buforze s wa-
ne tak dugo, jak funkcja, która je ustawia, nie zostanie za-
koczona. Zakoczenie nastpuje przez wykonanie instrukcji
return albo wykonanie skoku przez longjmp . Jest to zwiza-
ne z tym, e jeli wskanik stosu zostanie przesunity poniej
pozycji zapamitanej poprzednio, dane w nim zawarte mog
zosta nadpisane i nie bd prawidowe.
Uwaga: pewien problem dotyczy zmiennych automa-
tycznych (wewntrznych zmiennych funkcji, przechowywa-
nych w rejestrach). Jeli zmienimy zawarto takiej zmiennej
midzy wywoaniem funkcji setjmp a longjmp , jej zawar-
to moe by nieokrelona. Nie sposób okreli, czy zmien-
na ta jest w rejestrze, którego kopia znajduje si w jmp_buf ,
czy na stosie, odoona tam ju po zmianie.
Przykad: listing 238
for (;;) {}
return 0 ;
Dwie tytuowe funkcje, zdeklarowane w pliku <setjmp.h>
umoliwiaj wykonywanie skoków przez cay program. Ska-
kanie to wykonuje si w do specyficzny sposób. Obie funk-
cje korzystaj ze specjalnej zmiennej typu jmp_buf. Wywo-
anie funkcji setjmp zapisuje do niej aktualny stan procesora.
Obejmuje to rejestry, które nie s odtwarzane przez wywoy-
wan funkcj (s to rejestry r2-r17
}
oraz r28 i r29
– patrz
Listing 239 Pomocnicza funkcja wypisania linii
cz 12 ), rejestr SREG
oraz aktualn pozycj wierzchoka
stosu.
Póniejsze wywoanie funkcji longjmp, przy podaniu
jako pierwszy parametr zmiennej, która wczeniej zostaa
ustawiona przez setjmp , spowoduje nie tylko skok pod odpo-
wiedni adres, ale take przywrócenie stanu procesora.
Funkcja setjmp zwraca warto 0, jeli wanie j
wywoano i odpowieni stan zosta zapisany do bufora. Jeli
void DrawLine_P(prog_char *str)
{
lcd_DrawText( 0 , g_linia* 8 , str, 0xfc , 0 ,
LCD_TM_FLASH | LCD_TM_TRANSPARENT);
++g_linia;
lcd_Update();
}
Elektronika dla Wszystkich
41
215235220.082.png 215235220.093.png 215235220.104.png 215235220.115.png 215235220.001.png 215235220.012.png 215235220.023.png 215235220.034.png 215235220.036.png 215235220.037.png 215235220.038.png 215235220.039.png 215235220.040.png 215235220.041.png 215235220.042.png 215235220.043.png 215235220.044.png 215235220.045.png 215235220.046.png 215235220.047.png 215235220.048.png 215235220.049.png 215235220.050.png 215235220.051.png 215235220.052.png 215235220.053.png 215235220.054.png 215235220.055.png 215235220.056.png 215235220.057.png 215235220.058.png 215235220.059.png 215235220.060.png 215235220.061.png 215235220.062.png 215235220.063.png 215235220.064.png 215235220.065.png 215235220.066.png 215235220.067.png 215235220.068.png 215235220.069.png 215235220.070.png
Programowanie
teraz, gdzie wszystko si rozpoczo. Nastp-
nie dochodzimy do miejsca oznaczonego
kolorem ótym. Zapisywany jest stan proce-
sora. Poniewa funkcja setjmp zostaa wanie
wywoana – zwraca 0, przechodzimy do cz-
ci, w której wywoujemy funkcj funkcja_1 .
Funkcja ta wypisuje informacj, e rozpocz-
a swoje dziaanie, a nastpnie wywouje dru-
g funkcj. Std, po wypisaniu kolejnej infor-
macji o rozpoczciu dziaania funkcji, wyko-
nujemy daleki skok. Program rozpoczyna
swoje wykonywanie od miejsca, gdzie wywo-
alimy instrukcje setjmp . Teraz jednak zwró-
cona warto to nie zero, lecz liczba 33 – dru-
gi argument funkcji longjmp . Tym razem wy-
konujemy cz kodu odpowiedzialn za wy-
pisanie zwróconej wartoci i funkcja main si
koczy.
Spróbuj uruchomi program tak jak jest
oraz po usuniciu funkcji longjmp . Efekty
przedstawiaj fotografie 15 i 16. Widzisz ju
wygod stosowania mechanizmu dalekiego
skoku? To nie musi by koniecznie metoda na
obsug bdów – wszystko zaley od pomysu.
ABC... C
Diagnostyka – makro assert
albo w pliku makefile dopisujc do zmiennej CDEFS
warto -DNDEBUG.
Plik <assert. h> definiuje praktycznie tylko jedno makro:
makro assert . Makro to ma nastpujc posta:
assert (warunek);
Assert pochodzi w tym przypadku z angielskiego „za-
pewni”. I sowo to oddaje jego dziaanie. Jeli warunek
jest speniony, nasze makro nie zmienia przebiegu progra-
mu. Jednak w przypadku, gdy wynikiem sprawdzenia wa-
runku bdzie fasz, makro wypisze, na standardowe wyj-
cie bdu, informacj która moe mie nastpujc for-
m:
Assertion failed: (x < LCD_SX), function main, line 172.
a wykonywanie programu zostanie zatrzymane przez
wywoanie funkcji abort .
Jak widzisz, wypisana informacja daje peen zbiór da-
nych jakich potrzebujemy aby namierzy problem.
Makro assert stosuje si zwykle tylko w fazie urucha-
miania programu. Jzyk C zapewnia moliwo jego wy-
czenia w procesie kompilacji. W tym celu, przed do-
czeniem pliku <assert. h> naley zdefiniowa sta NDE-
BUG. Definicj tak najlepiej umieci w pliku nagów-
kowym który doczamy do wszystkich plików programu,
Znak koca linii
Makro assert jako znak koca linii wysya jedynie symbol
‘\n’ – nowa linia. Naley to uwzgldni w ustawieniach
terminala, albo tak napisa funkcj wysyajca znak, aby
w chwili wykrycia znaku ‘\n’ wysyaa wczeniej symbol
‘\r’ – powrotu karetki.
Specyfika mikrokontrolera
i AVR-GCC
W systemie mikroprocesorowym problemem moe by
potrzeba istnienia standardowego wyjcia bdu. Jednak,
w duym systemie, czsto bdziemy mieli specjalnie wy-
prowadzony port sucy do przeprowadzenia debugowa-
nia – wtedy uycie assert’a stanie si bardzo przyjemne.
AVR-GCC ma to do siebie, e aby informacja o b-
dzie zostaa wypisana, przed doczeniem nagówka
<assert. h> musi pojawi si linia:
#define __ASSERT_USE_STDERR
W innym przypadku jedyne co bdzie wykonane to za-
trzymanie programu, bez wypisania informacji o bdzie.
Przykad: listing 240
Listing 240 Sposób uycia makra assert
Debugowanie
Ostatnim, standardowym elemen-
tem C, jaki chc Ci przedstawi,
jest makro assert. Makro to okazu-
je si niezwykle przydatne w przy-
padku pisania duego kodu, tym
bardziej e zajmuje ono troch pa-
mici programu. Jednak jego za-
stosowanie moe da nieocenione
usugi w przypadku wystpienia
problemów, gdy bdziemy poszu-
kiwa ich róda.
Stosowanie makra assert , na
przykadzie naszego programu ge-
nerujcego symulacj ognia, poka-
zuje listing 240. Program przery-
wamy w chwili, gdy wylosowana
zostanie pozy-
cja x na rod-
ku wywietla-
cza. Nie ma to
wikszego
sensu prak-
tycznego, ale
umoliwia za-
obserwowanie dziaania testowanego makra.
Na czerwono zostay oznaczone linie ko-
nieczne do ustawienia standardowego wyjcia
bdu.
Rysunek 73 pokazuje dane wysane przez
procesor, przechwycone przez program termi-
nala.
Sensowne byoby na przykad spraw-
dzanie, czy parametry x oraz y, przekazy-
wane do funkcji lcd_Pixel, mieszcz si we
waciwym zakresie. Wykonanie tego zada-
nia za pomoc makra assert da nam moli-
wo testowania ich prawidowoci w cza-
sie uruchamiania i szybkie usunicie testo-
wania w programie bdcym ostateczn
wersj.
// Statyczne tworzenie „pliku“. Omawiane w czci 11
static FILE g_rsf_temp =
FDEV_SETUP_STREAM(rs_put, rs_get, _FDEV_SETUP_RW);
#define g_rsf (&g_rsf_temp)
(...)
// START programu
int main( void )
{
// Ustawienie wyjcia bdów
stderr = g_rsf;
// Konfiguracja portu RS
RS_SET_BAUD( 2400 );
UCSR0C = 1 <<URSEL0 | 1 <<UCSZ01 | 1 <<UCSZ00;
UCSR0A = 0 ;
UCSR0B = 1 <<RXEN0 | 1 <<TXEN0;
(...)
// Podsycanie ognia
uint8_t n;
for (n= 0 ; n< 80 ; n++)
{
Fot. 15 Przebieg dziaania programu
bez funkcji longjmp
(...)
assert(x != LCD_SX/ 2 - 1 );
}
(...)
Fot. 16 Przebieg dziaania programu
z funkcj longjmp
Rys. 73 Dziaanie makra assert
Mona spotka si z uyciem instrukcji da-
lekiego skoku w celu nadania programowi
pewnych cech wielowtkowoci. Nie jest to
najelegantsza droga i trzeba wtedy pamita
o problemie bdów stosu przy skoku do przo-
du – wzmianka o tym pojawia si przy okazji
omówienia funkcji. Istniej pewniejsze i bar-
dziej eleganckie metody pisania wielowtko-
wych programów.
Errata
W czasie ponaddwuletniego prowadzenia
kursu ustrzegem si wszystkich wpadek.
Dziki uwagom Czytelników w listach oraz
na forum udao si znale trzy bdy wyma-
gajce naprostowania. Co ciekawe, wszystkie
problemy skumuloway si w czci 5 kursu.
Póniej zwykle byy konsekwentnie kontynu-
owane.
Pojawiy si pewne drobne problemy ze
stosowaniem makra PSTR. Przy wywoywa-
niu naszych funkcji pojawiao si ostrzeenie:
warning: passing arg 1 of `LCDstr_P’
discards qualifiers from pointer target type.
42
Elektronika dla Wszystkich
215235220.071.png 215235220.072.png 215235220.073.png 215235220.074.png 215235220.075.png 215235220.076.png 215235220.077.png 215235220.078.png 215235220.079.png 215235220.080.png 215235220.081.png 215235220.083.png 215235220.084.png 215235220.085.png 215235220.086.png 215235220.087.png 215235220.088.png 215235220.089.png 215235220.090.png 215235220.091.png 215235220.092.png 215235220.094.png 215235220.095.png 215235220.096.png 215235220.097.png 215235220.098.png 215235220.099.png 215235220.100.png 215235220.101.png 215235220.102.png 215235220.103.png 215235220.105.png 215235220.106.png 215235220.107.png 215235220.108.png 215235220.109.png 215235220.110.png 215235220.111.png 215235220.112.png 215235220.113.png 215235220.114.png 215235220.116.png
 
Programowanie
Ostrzeenie to informuje nas, e wysanie ar-
gumentu pierwszego do funkcji wymaga po-
zbycia si kwalifikatora przypisanego do
wskanika. W naszym przypadku jest to kwa-
lifikator typu const. Do tej pory radzilimy
sobie z problemem, wykonujc rzutowanie
wyniku dziaania makra PSTR. Znacznie lep-
szym rozwizaniem jest nieco inne deklaro-
wanie i definiowanie funkcji piszcej. Odpo-
wiednie podejcie pokazuje listing 241.
Zmiany zaznaczyem kolorem. W tym przy-
padku rzutowanie nie bdzie potrzebne.
Take w czci 5 pojawio si nasze makro
generujce opónienie. Dla przypomnienia
pokazuj je na listingu 242.
W tym przypadku problem polega na tym,
e kompilator nie otrzymuje informacji, e za-
warto rejestru ticks zostanie utracona.
W efekcie, jeli wywoujemy delayus8 wp-
tli, tylko w pierwszym przebiegu warto
opónienia bdzie prawidowa. Aby temu
zapobiec, mona w ostatniej linii, jako typ pa-
rametru, zamiast „r” poda „+r”, co oznacza
parametr zarówno wejciowy, jak i wyjcio-
wy. W takim przypadku musi znale si on
za pierwszym dwukropkiem. Ponadto moe-
my podstawi tutaj element takiego typu, aby
moliwe byo zapisanie do niego wartoci –
tak wic nie moemy poda w miejscu t war-
toci staej. Problem ten rozwizuj, na dwa
róne sposoby, dwa kolejne listingi. W wik-
szoci przypadków taki zapis nie zwiksza
iloci generowanego kodu – poprzednio kom-
pilator automatycznie przypisywa podawan
warto do rejestru.
I po raz trzeci, i ostatni w czci 5, zajli-
my si interfejsem popularnego wy-
wietlacza alfanumerycznego. Okaza-
o si, e napisana wtedy funkcja ini-
cjacji wywietlacza w tryb czterobito-
wy zawiera bdy, które w niekorzyst-
nych przypadkach uniemoliwiajce
poprawne uruchomienie wywietlacza.
Prawidow funkcj pokazuje li-
sting 245. Bdy byy dwa. Powany,
polegajcy na pominiciu faktu, e
funkcja sendHalf przesya na wyjcie
modsz, a nie starsz poówk poda-
nego parametru. W tym przypadku
wystarczy odpowiednie przesunicie
podawanych komend. Reszta funkcji
korzysta z niej prawidowo. Drugim
bdem byo wywoanie konfiguracji
w trybie omiobitowym tylko dwa razy – we-
dug dokumentacji konieczne jest przesanie
jej trzykrotnie w czasie inicjacji.
Jak wielu Czytelników miao okazj si
przekona, dotychczasowe kody zazwyczaj
dziaay dobrze. Po poprawkach powinny
dziaa zgodnie z oczekiwaniami.
W tym miejscu dzikuj wszystkim za
cenne uwagi.
Podsumowanie
Dzisiejsza cz, bdca skadank kilku
drobniejszych, ale uytecznych informacji,
zamyka ostatecznie materia zaplanowany na
cykl kursu. W czasie trwania kursu zebrao si
troch informacji, wci przekadanych na na-
stpny miesic, ze wzgldu na brak miejsca.
Teraz materia zosta zamknity.
Kurs z krótkiego, w zamierzeniu, cyklu
przerodzi si w spore kompendium wiedzy.
Zabierajc si do pisania pierwszej czci, nie
spodziewaem si przedstawienia takiej iloci
materiau. Du przyjemno sprawia mi tym
bardziej zainteresowanie Czytelników i fakt,
e wród mikroprocesorowych projektów, po-
jawiajcych si na amach EdW, widz take
projekty pisanie w C.
Sympatyków cyklu ucieszy zapewne wia-
domo, e o ile jest to nasze ostatnie spotka-
nie w ramach regularnego kursu C, nie jest
ostatnim ogólnie.
Listing 241 Prawidowa funkcja piszca
void LCDstr_P(
const
prog_char* str)
{ (...)
}
Listing 242 Nie do koca prawidowa funkcja opónienia
#define delayus8(t)\
{ asm volatile( \
„delayus8_loop%=: \n\t”\
„nop \n\t“\
„dec %[ticks] \n\t”\
„brne delayus8_loop%= \n\t”\
: :[ticks]“r“(t) );}
Listing 243 Poprawione makro opónienia
Radosaw Koppel
radoslaw.koppel@elportal.pl
#define delayus8(t)\
{ \
uint8_t t_ = t; \
asm volatile( \
„delayus8_loop%=: \n\t”\
„nop \n\t”\
„dec %[ticks] \n\t”\
„brne delayus8_loop%= \n\t”\
: [ticks]”+r”(t_): );}
R E K L A M A
Listing 244 Realizacja opónienia jako funkcji rozwijalnej
static inline void delayus8 ( uint8_t t )
{
}
asm volatile (
„delayus8_loop%=: \n\t”
„nop \n\t”
„dec %[ticks] \n\t”
„brne delayus8_loop%= \n\t”
: [ ticks ] ”+r” ( t ): );
Listing 245 Poprawiona inicjacja wywietlacza
void lcd_init( void )
{
delay100us8( 150 );
PORT(LCD_RSPORT) &= ~( 1 <<LCD_RS);
lcd_sendHalf( (LCDC_FUNC|LCDC_FUNC8b)
>> 4
);
delay100us8( 41 );
lcd_sendHalf( (LCDC_FUNC|LCDC_FUNC8b)
>> 4
);
delay100us8( 2 );
lcd_sendHalf( (LCDC_FUNC|LCDC_FUNC8b) >> 4 );
lcd_sendHalf( (LCDC_FUNC|LCDC_FUNC8b) >> 4 );
delay100us8( 2 );
lcd_sendHalf( (LCDC_FUNC|LCDC_FUNC4b)
>> 4
);
delay100us8( 2 );
// Teraz jest ju 4b, koniec sendHalf
lcd_command(LCDC_FUNC|LCDC_FUNC4b|
LCDC_FUNC2L|LCDC_FUNC5x7);
lcd_command(LCDC_ON);
lcd_cls();
lcd_command(LCDC_MODE|LCDC_MODER);
lcd_command(LCDC_ON|LCDC_ONDISPLAY);
}
Elektronika dla Wszystkich
43
215235220.117.png 215235220.118.png 215235220.119.png 215235220.120.png 215235220.121.png 215235220.122.png 215235220.123.png 215235220.124.png 215235220.002.png 215235220.003.png 215235220.004.png 215235220.005.png 215235220.006.png 215235220.007.png 215235220.008.png 215235220.009.png 215235220.010.png 215235220.011.png 215235220.013.png 215235220.014.png 215235220.015.png 215235220.016.png 215235220.017.png 215235220.018.png 215235220.019.png 215235220.020.png 215235220.021.png 215235220.022.png 215235220.024.png 215235220.025.png 215235220.026.png 215235220.027.png 215235220.028.png 215235220.029.png 215235220.030.png 215235220.031.png 215235220.032.png 215235220.033.png 215235220.035.png
 
Zgłoś jeśli naruszono regulamin