kursC czesc018

background image

41

Programowanie

Elektronika dla Wszystkich

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

ABC... C

setjmp i longjmp – jak to dziaa

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
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

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-
ko czona. Zako czenie 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

Rys. 72 Propagacja bdów w
kolejnych wywoaniach funkcji

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

(...)

// 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

();

}

DrawLine_P

(PSTR(

„END“

));

for

(;;) {

{}

return

0

;

}

Listing 239 Pomocnicza funkcja wypisania linii

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

();

}

P

P

r

r

o

o

g

g

r

r

a

a

m

m

o

o

w

w

a

a

n

n

i

i

e

e

p

p

r

r

o

o

c

c

e

e

s

s

o

o

r

r

ó

ó

w

w

w

w

j

j





z

z

y

y

k

k

u

u

C

C

cz 18 – podsumowanie

background image

42

Programowanie

Elektronika dla Wszystkich

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.

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.

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
w i  k s z e g o
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.

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.

ABC... C

Diagnostyka – makro assert

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,

albo w pliku makefile dopisujc do zmiennej CDEFS
warto -DNDEBUG.

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

Fot. 15 Przebieg dziaania programu
bez funkcji longjmp

Fot. 16 Przebieg dziaania programu
z funkcj longjmp

Listing 240 Sposób uycia makra assert

// 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++)

{

(...)

assert

(x != LCD_SX/

2

-

1

);

}

(...)

Rys. 73 Dziaanie makra assert

background image

43

Programowanie

Elektronika dla Wszystkich

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 w p-
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.

Radosaw Koppel

radoslaw.koppel@elportal.pl

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

#define delayus8(t)\

{ \

asm volatile( \
„delayus8_loop%=: \n\t”\
„nop \n\t”\
„dec %[ticks] \n\t”\
„brne delayus8_loop%= \n\t”\

: [ticks]”+r”(t_): );}

uint8_t t_ = t; \

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 241 Prawidowa funkcja piszca

void

LCDstr_P

(

prog_char

* str)

{

(...)

}

const

Listing 245 Poprawiona inicjacja wywietlacza

void

lcd_init

(

void

)

{

delay100us8

(

150

);

PORT

(LCD_RSPORT) &= ~(

1

<<LCD_RS);

lcd_sendHalf

( (LCDC_FUNC|LCDC_FUNC8b)

);

delay100us8

(

41

);

lcd_sendHalf

( (LCDC_FUNC|LCDC_FUNC8b)

);

delay100us8

(

2

);

lcd_sendHalf

( (LCDC_FUNC|LCDC_FUNC8b) >

>>

4

);

lcd_sendHalf

( (LCDC_FUNC|LCDC_FUNC4b)

);

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);

}

>>

4

>>

4

>>

4

lcd_sendHalf

( (LCDC_FUNC|LCDC_FUNC8b) >>

4

);

delay100us8

(

2

);

R E K L A M A


Wyszukiwarka

Podobne podstrony:
kursC czesc007
kursC czesc006
kursC czesc001
kursC czesc003
kursC czesc006a przeprowadzka
kursC czesc000 programowanie
kursC czesc008
kursC czesc002
kursC czesc013
kursC czesc011
kursC czesc010
kursC czesc007
kursC czesc018

więcej podobnych podstron