kursC czesc006

background image

37

Programowanie

Elektronika dla Wszystkich

Podczas rozmowy o dalszych losach kursu,

który w³aœnie czytasz, przypomniano mi, ¿e

na p³ytce z kursu BASCOM istnieje jeszcze

element, którego do tej pory unika³em. Poja-

wi³a siê propozycja pe³nego wyczerpania

mo¿liwoœci p³ytki przed publikacj¹ nowego

uk³adu. Myœlê, ¿e idea powtórzenia wszyst-

kich æwiczeñ z kursu BASCOM-a mog³aby

nie wszystkim przypaϾ do gustu. Jednak

zabawa istniej¹cym na p³ytce przetwornikiem

analogowo-cyfrowym i cyfrowo-analogowym

bêdzie bardzo po¿ytecznym zajêciem. Zaj-

miemy siê wiêc dzisiaj magistral¹ I

2

C na

przyk³adzie obs³ugi uk³adu PCF8591. Ponad-

to chcê, abyœmy dziœ dokonali prze³omowej

zmiany w sposobie pisania programu - o tym

ju¿ w poni¿szym œródtytule.

Dzielenie kodu programu

Do tej pory ca³y kod naszego programu

umieszczaliœmy w jednym pliku. Podejœcie

takie jest uzasadnione w przypadku pisania

niewielkich aplikacji. W praktyce jednak im

program bardziej skomplikowany, tym bar-

dziej op³acalne staje siê jego podzielenie na

pewne funkcjonalne bloki. Znacznie pomaga

to w utrzymaniu porz¹dku. Móg³bym uraczyæ

Ciê niepotrzebn¹ dawk¹ teorii, zamiast tego

zaczniemy jednak pisaæ nowy „podzielony”

program. Umo¿liwi to samodzielne przekona-

nie siê, o co w tym wszystkim chodzi i dla-

czego jest takie genialne.

Na rysunku 27 pokaza³em, w jaki sposób

bêdzie przebiegaæ kompilacja naszego dzi-

siejszego programu. Podobne, tylko bardziej

ogólne, obrazki znajdziesz tak¿e w innych

kursach jêzyka C. Przyjrzyj siê teraz wspom-

nianemu rysunkowi. Umówmy siê teraz, ¿e

ka¿dy element, który poddawany bêdzie

oddzielnemu procesowi kompilacji, nazwiemy

modu³em. Na nasze dzisiejsze potrzeby stwo-

rzymy modu³y o nazwach: lcd, i2c oraz delay.

Jak ³atwo siê domyœliæ, zostan¹w nich

umieszczone elementy niezbêdne do obs³ugi

wyœwietlacza, magistrali I

2

C oraz podprogra-

my opóŸnieñ. Dodatkowo pojawiaj¹ siê dwa

pliki nag³ówkowe, które nie s¹ elementem

¿adnego modu³u. W pliku harddef.h zostan¹

umieszczone wszystkie informacje dotycz¹ce

konfiguracji sprzêtowej czêœci naszego urz¹-

dzenia - bêd¹ to g³ównie przypisania symbo-

licznych oznaczeñ odpowiednim wyprowa-

dzeniom. Nietrudno siê domyœliæ, co znajdzie

siê w pliku makra.h. Wprowadzimy tutaj

przede wszystkim poznane w poprzedniej

czêœci makra upraszcza-

j¹ce dostêp do portów.

Pliki hardder.h oraz

makra.h

do³¹czamy

wszêdzie tam, gdzie

informacje o wyprowa-

dzeniach oraz odpo-

wiednie makra s¹ nam

potrzebne. Dziêki takie-

mu podejœciu wszystkie

niezbêdne makra napi-

szemy tylko raz - nie

bêdziemy musieli kop-

iowaæ ich wszêdzie,

gdzie bêd¹ one koniecz-

ne jeœli postanowimy w

jakiœ sposób je ulep-

szyæ, zmiany pojawi¹ siê natychmiast we

wszystkich modu³ach. Definicja czêœci sprzê-

towej w oddzielnym pliku, poza powy¿szymi

zaletami, ma jeszcze jedn¹ - jest czytelna.

Jeœli zmienimy coœ w po³¹czeniach na p³ytce,

zawsze wiemy, gdzie szukaæ linijek wymaga-

j¹cych modyfikacji.

Zauwa¿, ¿e nag³ówków harddef.h oraz

makra.h nie do³¹czamy do modu³u delay.

Modu³ ten nie potrzebuje dostêpu do portów,

nie korzysta wiêc z zawartych tam informacji.

W zwi¹zku z tym do³¹czanie wymienionych

plików jest zbêdne.

Wspomniane modu³y nie bêd¹ w stanie

dzia³aæ same z siebie. Bêd¹ one tylko zbiorem

odpowiednich funkcji które trzeba jeszcze

wywo³aæ w odpowiedniej kolejnoœci oraz

z odpowiednimi parametrami. Tym zajmie siê

modu³ nazwany main. Zauwa¿, ¿e nie ma on

przypisanego pliku nag³ówkowego. Jest to

zwi¹zane z tym, ¿e nie udostêpniamy ¿adnych

funkcji z modu³u main na zewn¹trz. To st¹d

bêdziemy wywo³ywaæ wszystkie zewnêtrzne

funkcje, nie na odwrót.

Kompilacja programu w C przebiega w

trzech fazach. Na samym pocz¹tku nastêpuje

³¹czenie ze sob¹ odpowiednich plików

nag³ówkowych z plikiem Ÿród³owymi. Makra

oraz sta³e symboliczne s¹ zamieniane na zde-

finiowane formy (rozwijane). Usuwane s¹ tak

zwane bia³e znaki (spacje, tabulatory, znaki

koñca linii). Dzia³ania te wykonuje preproce-

sor. Nastêpnie tak obrobiony plik jest kompi-

lowany. Jeœli dzia³anie to przebieg³o bezb³êd-

nie, jego wynikiem jest przynajmniej plik

z rozszerzeniem *.o. Kompilator umieszcza

w nim relokowalny kod maszynowy. Jest to

ju¿ praktycznie kod programu, jednak zapisa-

ny w taki sposób, ¿e funkcje nie maj¹ przypo-

rz¹dkowanych miejsc w pamiêci. W pliku

zawarte s¹ dodatkowo informacje o tym, jakie

funkcje on zawiera - umo¿liwia to ich wywo-

³anie za pomoc¹ nazwy z poziomu innych

modu³ów tego samego projektu.

Jeœli wybrana zosta³a odpowiednia opcja,

kompilator utworzy dodatkowo plik zawiera-

j¹cy listing modu³u w kodzie asemblera. Jeœli

zachowa³eœ kody programów z poprzednich

czêœci, mo¿esz podejrzeæ teraz odpowiednie

pliki o rozszerzeniach *.lst.

Poprzednie dwie fazy odbywaj¹ siê

oddzielnie dla ka¿dego pliku Ÿród³owego pro-

jektu. Ostatnim krokiem, dziel¹cym kod pro-

gramu od jego postaci umo¿liwiaj¹cej zapro-

gramowanie mikrokontrolera, jest linkowa-

nie. W procesie tym podejmowana jest decyz-

ja, w jakim dok³adnie miejscu umieszczone

maj¹ byæ zmienne, gdzie umieszczone maj¹

byæ funkcje, uzupe³niane s¹ wymagaj¹ce tego

instrukcje skoków. Efektem pracy linkera s¹

obrazy pamiêci programu oraz danych EEP-

ROM. Dodatkowo, jeœli odpowiednie opcje

zosta³y wybrane, pojawi¹ siê pliki *.lss

zawieraj¹ce listing ca³ego programu, *.map

z opisem, pod jakimi adresami w pamiêci

zosta³y umieszczone poszczególne elementy,

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êœæ 6

Tylko w jednym pliku Ÿród³owym mo¿e

znaleŸæ siê tylko jedna funkcja o nazwie

main. To od niej zacznie siê wykonywanie

programu. Dla kompilatora nie ma znacze-

nia, w którym module zostanie ona umiesz-

czona. Dla w³asnej wygody oraz czytelnoœ-

ci kodu umieszczamy j¹ w module g³ów-

nym programu.

Rysunek 27 - przebieg kompilacji dzisiejszego programu

background image

oraz *.elf i *.sym - zawieraj¹ce informacje

u³atwiaj¹ce symulacje programu.

Jeœli chcesz, zobacz, jak wygl¹daj¹ po-

szczególne pliki - otwórz je w notatniku albo

w naszym Programmers Notepadzie.

Zobacz, jaka jest ró¿nica miêdzy plikami

*.lst a *.lss.

Zanim zaczniesz

Zanim zaczniesz pisaæ podawane dalej kody,

koniecznie utwórz nowy katalog. W moim

przypadku nazywa³ siê on PCF8591. Mo¿esz

skopiowaæ do niego wzorzec pliku makefile

i wykonaæ w nim standardowe zmiany - jed-

nak aby ca³oœæ dzia³a³a prawid³owo, koniecz-

na bêdzie jego dalsza edycja - napiszê o tym

po przedstawieniu kodów programu. Stwórz

w Programmers Notepadzie nowy projekt

i zapisz go w utworzonym katalogu. Pisane

pliki dodawaj od razu do projektu.

Pierwszy modu³ - delay

Tworzenie modu³ów proponujê rozpocz¹æ od

elementu najprostszego - funkcji opóŸnieñ.

Konieczne jest utworzenie pliku nag³ówko-

wego oraz pliku kodu Ÿród³owego. Aby lepiej

zrozumieæ dalej przedstawiane listingi, zaj-

rzyj do ramki „Co i jak umieœciæ w nag³ów-

ku”.

Oba pliki, jakie musisz teraz napisaæ,

pokazuj¹ listingi 35 i 36. Zauwa¿, ¿e nie ma

tutaj nic nowego. Dok³adnie takie funkcje

opóŸnieni by³y ju¿ przez nas napisane. Mo¿e

zainteresuje Ciê nowe makro delay500ns. Za-

pis taki powoduje wykonanie skoku do na-

stêpnej instrukcji. Co nam to daje? OpóŸnie-

nie dwóch cykli, przy zajêciu tylko jednego

s³owa programu. Prosta sztuczka poprawiaj¹-

ca optymalnoϾ kodu.

G³ówn¹ ró¿nic¹ miêdzy tym, co robiliœmy

do tej pory, jest podzielenie pliku. Zauwa¿, ¿e

definicja funkcji zosta³a

umieszczona w pliku kodu

Ÿród³owego.

Do pliku Ÿród³owego do-

³¹czam nag³ówek tego samego

modu³u. Regu³¹ jest, aby robiæ

to zawsze, chocia¿ czasami

nic nam to nie daje. W tym przypadku jednak

potrzebujemy zawartych w delay.h makr.

Makra

oraz definicja sprzêtu

Pierwszy modu³ móg³ byæ napisany bez wg³ê-

biania siê w to, co siedzi w plikach makra.h

oraz harddef.h, poniewa¿, jak to zosta³o ju¿

wspomniane - modu³ ten z nich nie korzysta.

Jednak przed pójœciem dalej powinniœmy

utworzyæ wspomniane pliki. Jedyna nowoœæ na

listingu 37 to sposób zapisu. Z tego powodu

nie bêdê siê wg³êbia³ w jego opis. Nie przejmuj

siê pojawiaj¹c¹ siê na listingu 38 tajemnicz¹

sta³¹ I2C_SPEED. Wszystko stanie siê jasne

przy okazji omawiania modu³u i2c.

Ze wzglêdu na wygodê, kody przedsta-

wiam w ca³oœci. Chcê jednak, abyœ wiedzia³,

¿e o ile plik z makrami zosta³ stworzony „od

rêki”, to jednak plik z definicj¹ sprzêtu by³

rozwijany, w ramach potrzeb, podczas pisania

kolejnych modu³ów. Oba pliki nale¿y trakto-

waæ w³aœnie w taki sposób - dodawaæ do nich

nowe wpisy w razie potrzeby.

Wyœwietlacz LCD

Poniewa¿ ostatnio napisaliœmy ju¿ procedury

obs³ugi wyœwietlacza LCD, wykorzystajmy

je teraz w celu stworzenia interfejsu u¿yt-

kownika.

Poza plikiem g³ównym, dobrym zwycza-

jem jest zaznaczanie w nazwie funkcji

z jakiego pliku ona pochodzi. Znakomicie

upraszcza to orientacjê w programie, zw³asz-

cza jeœli przysiadamy do niego po d³u¿szym

czasie. Nie ma jakiejœ sztywnej regu³y, jak

takiego oznaczenia dokonywaæ - kompilator

zupe³nie siê tym nie interesuje. Polecam Ci

!&

Programowanie

Elektronika dla Wszystkich

ABC... C

Co i jak umieœciæ w nag³ówku

Pliki nag³ówkowe mo¿emy, dla w³asnych potrzeb,

podzieliæ na dwa rodzaje: te, które maj¹ przyporz¹dko-

wany plik kodu Ÿród³owego, oraz takie, które istniej¹

samodzielnie. Jest to podzia³ tworzony tylko przez nas

- kompilator nie interesuje siê tym, czy plik nag³ówko-

wy posiada jakiœ plik Ÿród³owy o identycznej nazwie.

W pliku na-

g³ówka umieszcza-

my:

• Definicje sta³ych.

Jednak jeœli s¹ one

przeznaczone tylko

dla danego modu³u

i reszta programu

nie potrzebuje ich

znaæ, sta³e takie

mo¿na umieszczaæ

bezpoœrednio

w

pliku Ÿród³owym.

• Makra, z identycznym zastrze¿eniem jak wy¿ej.

• Funkcje statyczne, traktowane jako makra i z takim

samym, jak dotycz¹ce ich, zastrze¿eniem. Teraz mo¿e

nie jest to jasne, ale wyjaœni siê gdy tylko dowiesz siê,

czym s¹ funkcje statyczne.

Dodatkowo, tylko w przypadku plików skojarzonych

z plikami Ÿród³owymi:

• Deklaracje funkcji, które chcemy udostêpniæ do

wywo³ywania z zewn¹trz. Deklaracja taka teoretycz-

nie mo¿e pojawiæ siê w dowolnym pliku nag³ówko-

wym. Mo¿na nawet powtarzaæ j¹ bezpoœrednio w pli-

ku Ÿród³owym, tam, gdzie jest ona potrzebna. Jednak

ze wzglêdu na czytelnoœæ kodu nie robi siê tego. Dek-

laracje takie umieszczaj zawsze w pliku nag³ówko-

wym o nazwie identycznej z nazw¹ pliku Ÿród³owego,

w którym znajduje siê definicjê funkcji.

Szkielet pliku nag³ówkowego

Na listingu w tej ramce przedstawiam, w jaki sposób

prawid³owo utworzyæ plik nag³ówkowy. Bardzo

wa¿ne s¹ pojawiaj¹ce siê tutaj instrukcje preprocesora.

S¹ to instrukcje kompilacji warunkowej. W takim

zastosowaniu jak przedstawia listing zapewniaj¹ one,

¿e zawartoœæ pliku zostanie przetworzona tylko raz.

Jest to cenna w³aœciwoœæ w du¿ych projektach, gdzie

czasami w samym pliku nag³ówkowym musimy do³¹-

czyæ plik, który sam do³¹cza plik, w którym w³aœnie

jesteœmy. W takim przypadku zosta³aby zg³oszona

masa b³êdów oznaczaj¹cych, ¿e sta³e zosta³y ju¿ zde-

finiowane, a funkcje zdeklarowane. (...) W praktyce

powi¹zania takie mog¹ byæ du¿o dalsze i trudniejsze

do wykrycia - wykorzystanie kompilacji warunkowej

rozwi¹zuje problem w sposób uniwersalny i eleganc-

ki.

Oczywiœcie aby ca³oœæ dzia³a³a prawid³owo, ka¿dy

plik musi pos³ugiwaæ siê innym identyfikatorem.

Zwyk³o siê wykorzystywaæ w nim nazwê pliku oraz

s³owo INCLUDED (ang. „do³¹czony”). Na przyk³ad

dla pliku delay.h bêdzie to: DELAY_H_INCLUDED.

Poleceniu #ifndef równowa¿ny jest zapis: #if

!defined.

ABC... C

Funkcja itoa

Funkcja itoa nie nale¿y do standardu jêzyka C. Jest

jednak na tyle popularna, ¿e wiele kompilatorów j¹

udostêpnia. W AVR-GCC wymaga ona do³¹czenia

nag³ówka <stdlib.h>. itoa zamienia podan¹ liczbê

(pierwszy argument) na napis, który wpisuje do bufo-

ra (drugi argument). Liczba jest przekszta³cana z baz¹

podan¹ jako trzeci argument (na listingu 39 jest to

baza dziesiêtna). Jeœli baza przekszta³canej liczby jest

równa 10, a przekszta³cana liczba ujemna - przed

wynikiem zostanie umieszczony znak ‘-’. Maksymal-

na baza, na jak¹ zezwala nasz kompilator, to 36 (jed-

nak nie jest to ujête w ¿aden standard). Jeœli baza jest

wiêksza ni¿ 10, po cyfrze ‘9’ wyœwietlana jest litera

‘a’.

Powy¿sze informacje pozwalaj¹ nam obliczyæ

niezbêdn¹ wielkoœæ bufora. Dla szesnastobitowych

liczb ze znakiem, wynikiem o maksymalnej d³ugoœci

jest: -32768. Daje to 6 znaków plus znak zerowy.

Funkcja zwraca zawsze swój drugi argument

(wskaŸnik do ³añcucha). Umo¿liwia to jej wstawie-

nie bezpoœrednio w miejsce odpowiedniej zmiennej

(tak jak zosta³o to zrobione na listingu 39).

Listing 35 - plik delay.h

#ifndef DELAY_H_INCLUDED

#define DELAY_H_INCLUDED

#define delay250ns() {asm volatile(„nop„::);}

#define delay500ns()\

{asm volatile( \

„rjmp exit%=\n\t„\

„exit%=:\n\t„::);

#define delayus8(t)\

{asm volatile( \

„delayus8_loop%=: \n\t„\

„nop \n\t„\

„dec %[ticks] \n\t„\

„brne delayus8_loop%= \n\t„\

: :[ticks]„r„(t) );}

// DEC - 1 cykl, BRNE 2 cykle, + 1xnop. Zegar 4MHz

// Deklaracja funkcji o najd³u¿szym opóŸnieniu

void

delay100us8(uint8_t t);

#endif

//DELAY_H_INCLUDED

Listing 36 - plik delay.c

#include <avr\io.h>

#include „delay.h„

void

delay100us8(uint8_t t)

{

while

(t>

0

)

{

delayus8(

100

);

—t;

}

}

////////////////////////////////

// skrótowy opis pliku

//

// Autor, kompilator

////////////////////////////////

#ifndef IDENTYFIKATOR

#define IDENTYFIKATOR

// makra, sta³e, deklaracje...

#endif

//IDENTYFIKATOR

background image

dodawanie przed nazw¹ funkcji nazwy pliku

oraz znaku podkreœlenia. Mog¹ byæ to zarów-

no du¿e, jak i ma³e litery. Dla porz¹dku staraj

siê jednak przyj¹æ jedn¹ wersje w obrêbie

ca³ego programu. Stwórz plik lcd.c i skopiuj do

niego napisane przy okazji zesz³ego kursu

funkcje obs³ugi wyœwietlacza. Przyjêty sposób

oznaczañ wymaga zmiany nazw wszystkich

znajduj¹cych siê tutaj funkcji wyœwietlacza.

Pomocna w tym przypadku bêdzie komenda

„Edit->Replace” Programmers Notepada. Nie

pozwalaj jednak na w pe³ni automatyczn¹

zamianê, poniewa¿ w ten sposób pozmieniasz

tak¿e nazwy wszystkich sta³ych. Nie ma tego

du¿o, dlatego te¿ sprawdzenie ka¿dej propono-

wanej zmiany nie zajmie du¿o czasu.

Przyjrzyj siê listingowi 39. Ramkami

zaznaczy³em miejsca gdzie dodane zosta³y

nowe elementy. Wprowadzone zosta³y obie

funkcje pisz¹ce tekst, które utworzyliœmy

ostatnio. Dodatkowo utworzono funkcjê

wypisuj¹c¹ podan¹ liczbê. Jej dzia³anie opie-

ra siê na funkcji itoa, któr¹ opisuje odpowied-

nia ramka.

Poza tymi drobnymi funkcjami z pliku

zosta³y usuniête definicje komend, opóŸnieñ

oraz makr pomocniczych. Zamiast tego wpro-

wadzone zosta³y odpowiednie nag³ówki.

Definicje komend oraz wszystkich funkcji,

które chcemy udostêpniæ na zewn¹trz, znajdu-

j¹ siê na listingu 40. Wpisz jego zawartoœæ do

pliku lcd.h.

Interfejs I

2

C - sposób

przesy³ania danych

Podczas pisania tego fragmentu programu

podpiera³em siê dokumentacj¹ avr300 dostêp-

n¹ kiedyœ na stronie firmy ATMEL. Zawarto

w niej informacje o realizacji programowego

interfejsu I

2

C. Mimo ¿e dokumentacja ta znik-

nê³a ze strony producenta AVR-ów, nadal

mo¿na znaleŸæ j¹ za pomoc¹ wiêkszoœci

wyszukiwarek internetowych. Pomocna oka-

za³a siê tak¿e dokumentacja przetwornika

PCF8591. Wszystkie te materia³y s¹ udostêp-

nione na Elportalu.

I

2

C jest dwuliniowym interfejsem synch-

ronicznym. Wykorzystywane linie to linia

danych (SDA) oraz linia zegara (SCL). Odpo-

wiednie wyjœcia uk³adów powinny byæ

wyprowadzeniami typu otwarty kolektor,

podci¹ganie linii do stanu „1” (bêd¹cego sta-

nem spoczynkowym) odbywa siê za pomoc¹

zewnêtrznych rezystorów (rzêdu 4,7kΩ).

W sieci tworzonej za pomoc¹ interfejsu I

2

C

istniej¹ dwa typy urz¹dzeñ: urz¹dzenia MAS-

TER (nadrzêdne) i SLAVE (podrzêdne). To

MASTER inicjuje transmisje, wybiera uk³ad

oraz przesy³a do niego dane. SLAVE mo¿e

wysterowaæ liniê danych, tylko jeœli otrzyma

odpowiednie polecenie. Tylko MASTER

mo¿e generowaæ sygna³ zegarowy.

!'

Programowanie

Elektronika dla Wszystkich

Listing 39 - plik lcd.c

Listing 40 - plik lcd.h

#ifndef LCD_H_INCLUDED

#define LCD_H_INCLUDED

// Komendy steruj¹ce

#define LCDC_CLS

0x01

#define LCDC_HOME

0x02

#define LCDC_MODE

0x04

#define LCDC_MODER

0x02

#define LCDC_MODEL

0

#define LCDC_MODEMOVE

0x01

#define LCDC_ON

0x08

#define LCDC_ONDISPLAY

0x04

#define LCDC_ONCURSOR

0x02

#define LCDC_ONBLINK

0x01

#define LCDC_SHIFT

0x10

#define LCDC_SHIFTDISP

0x08

#define LCDC_SHIFTR

0x04

#define LCDC_SHIFTL

0

#define LCDC_FUNC

0x20

#define LCDC_FUNC8b

0x10

#define LCDC_FUNC4b

0

#define LCDC_FUNC2L

0x08

#define LCDC_FUNC1L

0

#define LCDC_FUNC5x10

0x04

#define LCDC_FUNC5x7

0

#define LCDC_CGA

0x40

#define LCDC_DDA

0x80

// Deklaracje funkcji

void

lcd_command(uint8_t command);

void

lcd_data(uint8_t data);

void

lcd_cls(

void

);

void

lcd_home(

void

);

void

lcd_init(

void

);

void

lcd_str_P(prog_char* str);

void

lcd_str(

char

* str);

void

lcd_dec(

int

val);

#endif

//LCD_H_INCLUDED

Listing 38 - plik harddef.h

#ifndef HARDDEF_H_INCLUDED

#define HARDDEF_H_INCLUDED

// Interfejs I2C

#define I2C_SDAPORT D

#define I2C_SDA 6

#define I2C_SCLPORT D

#define I2C_SCL 5

#define I2C_SPEED 100000

// Definicje wyprowadzeñ wyœwietlacza

#define LCD_RS 2

#define LCD_RSPORT B

#define LCD_E 1

#define LCD_EPORT B

#define LCD_DPORT B

#define LCD_D4 4

//D5,D6,D7 kolejno

#endif

//HARDDEF_H_INCLUDED

Listing 37 - plik makra.h

#ifndef MAKRA_H_INCLUDED

#define MAKRA_H_INCLUDED

// Makra upraszczaj¹ce dostêp do portów

// *** Port

#define PORT(x) XPORT(x)

#define XPORT(x) (PORT##x)

// *** Pin

#define PIN(x) XPIN(x)

#define XPIN(x) (PIN##x)

// *** DDR

#define DDR(x) XDDR(x)

#define XDDR(x) (DDR##x)

// NOPek

#define NOP() {asm volatile(„nop„::);}

#endif

//MAKRA_H_INCLUDED

background image

Pe³ny standard definiuje mo¿liwoœæ wyst¹-

pienia w systemie kilku uk³adów typu MAS-

TER. Stworzono specjalny protokó³ umo¿li-

wiaj¹cy bezkonfliktow¹ ich wspó³pracê.

W rzeczywistych uk³adach najczêœciej jednak

chcemy mieæ dostêp tylko do wybranych

uk³adów wykonawczych. Umo¿liwia to

znaczne uproszczenie algorytmu: Po pierw-

sze, znika koniecznoϾ badania, czy linia jest

wolna albo czy nasza transmisja nie zosta³a

przerwana; Po drugie, mo¿emy zrezygnowaæ

z linii SCL w trybie otwartego kolektora -

tylko nasz procesor mo¿e generowaæ sygna³

zegarowy. Upraszczaj¹c nasze rozwi¹zanie,

odejdziemy jeszcze troszkê od standardu. Zre-

zygnujemy z zewnêtrznego rezystora podci¹-

gaj¹cego na korzyœæ wewnêtrznego podci¹ga-

nia portów mikrokontrolera. Praktyka wyka-

zuje, ¿e takie rozwi¹zanie nie zak³óca dzia³a-

nia do³¹czonego uk³adu.

Podczas przesy³ania danych linia SDA nie

mo¿e siê zmieniaæ, gdy na linii SCL znajduje

siê stan wysoki (rysunek 28). Zmiana linii

danych podczas wysokiego stanu na wypro-

wadzeniu zegarowym ma specjalne znaczenie

i umo¿liwia generowanie tak zwanych warun-

ków START i STOP - pokazuje to rysunek

29. Stanem spoczynkowym obu linii jest stan

wysoki. Tylko miêdzy warunkami STOP

i START mo¿liwe jest utrzymanie innego ich

stanu.

Poniewa¿ na tej samej magistrali mo¿e

wspó³pracowaæ do 128 uk³adów, zaraz po roz-

poczêciu transmisji konieczne jest wys³anie

adresu uk³adu, do którego chcemy uzyskaæ

dostêp, oraz informacji, czy chcemy dokonaæ

zapisu, czy odczytu. Zaadresowany element

zg³asza swoj¹ gotowoœæ poprzez wystawienie

impulsu ACK (ACKnowledge - potwierdze-

nie). Polega to na wystawieniu 0 na jeden takt

sygna³u zegarowego. Podczas transmisji to

zawsze odbiorca potwierdza odbiór danych

(tak wiêc przy odczycie jest to MASTER,

przy zapisie bêdzie to SLAVE). Po odczytaniu

ostatniej danej, przed wygenerowaniem wa-

runku STOP, nie nale¿y generowaæ potwier-

dzenia. Brak sygna³u ACK powinien powodo-

waæ przerwanie transmisji.

Rysunek 30 przedstawia sposób przesy³a-

nia potwierdzenia. Na rysunku 31 natomiast

przedstawiam wygl¹d podstawowych ramek

danych stosowanych podczas transmisji.

Rysunek ten na pierwszy rzut oka mo¿e stra-

szyæ swoim wygl¹dem. Postaram siê krótko

rozwiaæ wszelkie w¹tpliwoœci. Widzimy tutaj,

¿e zgodnie z powy¿szym opisem, po warunku

START wysy³amy na magistralê siedem

bitów adresu. Ósmy bit w przesy³anym,

pierwszym bajcie musi mieæ wartoœæ: 1 - jeœli

chcemy odczytaæ dane; 0 - jeœli chcemy doko-

naæ zapisu. Uk³ad powinien potwierdziæ pra-

wid³owy adres sygna³em ACK. Niezale¿nie

od tego, czy dokonujemy zapisu, czy odczytu,

to MASTER generuje sygna³ zegarowy. Przy

zapisie na wyprowadzenie SDA podajemy

kolejne bity danych, od najstarszego do naj-

m³odszego. Ka¿dy prawid³owo zapisany bajt

SLAVE potwierdza sygna³em ACK. Jeœli

ACK siê nie pojawi, oznaczaæ to mo¿e koniec

rejestrów przeznaczonych do zapisu, zape³-

nienie pamiêci lub b³¹d transmisji. Jeœli dane

chcemy odczytywaæ, na linii SDA nale¿y

utrzymywaæ stan wysoki - bêdzie ona wyste-

rowywana przez urz¹dzenie SLAVE. Wczasie

odczytu to na nas spoczywa odpowiedzial-

noœæ za generowanie sygna³u ACK. Ostatnie-

go odczytanego bajtu nie potwierdzamy!

Zwykle transmisja koñczy siê warunkiem

STOP. Przyjrzyj siê jednak ostatniej ramce.

Okazuje siê, ¿e transmisja mo¿e byæ tak¿e

„zakoñczona” kolejnym STARTem. Umo¿li-

wia to, przede wszystkim, zmianê trybu

odczyt/zapis bez zwalniania magistrali.

Uff... Tyle jeœli chodzi o sam sposób trans-

misji. Jeœli jeszcze coœ jest dla Ciebie niejas-

ne, spróbuj przyjrzeæ siê ponownie rysunkom,

przeczytaj ponownie opis. Nie œpiesz siê.

Trudniejsze zagadnienia powinny staæ siê

jasne, gdy pojawi¹ siê kody Ÿród³owe.

SzybkoϾ transmisji

Standard definiuje aktualnie trzy mo¿liwe

prêdkoœci transmisji: standard (100kbps), fast

(400kbps) oraz high speed (3,4Mbps). W ka¿-

dym przypadku ograniczona jest jedynie mak-

symalna prêdkoœæ przesy³anych danych, tak

wiêc bezpiecznie mo¿emy korzystaæ z uk³a-

dów typu high speed, taktuj¹c transmisjê

z czêstotliwoœci¹ choæby 1kHz. Jednak próba

transmisji z wiêksz¹ prêdkoœci¹ ni¿ ta, do któ-

rej uk³ad zosta³ przystosowany, najpradpo-

dobniej zakoñczy siê b³êdami transmisji. Pod-

sumowuj¹c, w prostym przypadku, moglibyœ-

my stworzyæ procedury obs³ugi I

2

C dla

najwolniejszego przypadku i nie przejmowaæ

siê reszt¹. Jednak nie zapominajmy o tym,

¿e naszym podstawowym celem nie jest

"

Programowanie

Elektronika dla Wszystkich

Rysunek 29 - generowanie warunku

START i STOP

Rysunek 30

Rysunek 28 - transmisja jednego bitu

Rysunek 31 - ramki danych

background image

„odklepanie sprawy i zapomnienie o proble-

mie”. Spróbujemy stworzyæ coœ bardziej uni-

wersalnego.

Powiedzmy sobie najpierw o tym, jak od

strony programu bêdzie wygl¹da³a transmisja

jednego bajtu. Sprawa jest prosta:

1. Miêdzy warunkami START i STOP,

w przerwie miêdzy bajtami, linia zegara znaj-

duje siê w stanie niskim.

2. Zmieniamy stan linii danych i odczekujemy

po³owê okresu wynikaj¹cego z szybkoœci

transmisji - odczekanie pewnego czasu jest

konieczne przed wygenerowaniem narastaj¹-

cego zbocza zegara.

3. Generujemy impuls wysoki na linii zegara

o czasie trwania równym pozosta³ej nam

po³owie okresu.

4. Mo¿liwy jest natychmiastowy powrót do

punktu 2 - na magistrali nie trzeba podtrzy-

mywaæ danych.

Jak ³atwo zauwa¿yæ, konieczne bêdzie

obliczenie wystêpuj¹cego wy¿ej po³ówkowe-

go opóŸnienia. W praktyce, aby transmisja

mog³a przebiegaæ z pe³n¹ szybkoœci¹, na

potrzebê warunków START i STOP mo¿na

stworzyæ oddzieln¹ funkcjê o opóŸnieniu

„æwiartkowym”, jednak w tym przyk³adzie

spowolnimy odrobinê szybkoœæ dzia³ania

magistrali, uzyskuj¹c w zamian uproszczenie

kodu. Konieczne opóŸnienie mo¿emy obli-

czyæ ze wzoru:

n = ( F_CPU - X ) / I2C_SPEED / 2

Uzyskamy wynik bezpoœrednio w cyklach

maszynowych. F_CPU jest sta³¹ do³¹czan¹ z

poziomu pliku makefile, I2C_SPEED zdefi-

niowaliœmy na listingu 38. Zapytanie mo¿e

wywo³aæ X. W tym miejscu powinna pojawiæ

siê liczba cykli, jakie zajmuje procesorowi

zmiana stanu linii danych. W najprostszym

przypadku mo¿emy j¹ pomin¹æ. Wp³yw tej

wartoœci jest widoczny jedynie dla prêdkoœci

transmisji porównywalnych z czêstotliwoœci¹

pracy mikroprocesora. W takim przypadku

nasze obliczenia i tak obarczone bêd¹ du¿ym

b³êdem - zmiana stanu linii to nie wszystko

czym musimy siê zaj¹æ.

I

2

C - funkcja opóŸnienia

po³ówkowego

Stwórz i dodaj teraz do projektu plik i2c.c

oraz i2c.h. Plik nag³ówkowy pozostawimy

chwilowo pusty i zajmiemy siê plikiem Ÿród-

³owym.

Na pocz¹tku do³¹czamy wszystkie

potrzebne nag³ówki - patrz listing 41.

Po tej czynnoœci obliczymy niezbêdn¹

wartoœæ opóŸnienia oraz stworzymy odpo-

wiedni¹ funkcjê. Zale¿y nam na tym, aby

obliczanie opóŸnienia oraz wybór

odpowiedniej wersji funkcji odby-

wa³ siê automatycznie. Informacje

przydatne do zrozumienia tej czêœ-

ci kodu znajduj¹ siê w trzech kolej-

nych ramkach: „Kompilacja

warunkowa jako sposób na

zwiêkszenie

uniwersalnoœci

kodu”, „Funkcje static i inline”

oraz „Generowanie w³asnych

b³êdów i ostrze¿eñ”. Kod przed-

stawiam na listingu 42.

Listing ten mo¿e wygl¹daæ na

skomplikowany. Zatrzymajmy siê

na nim przez chwilê. Jeœli przeczy-

ta³eœ wspomnian¹ na pocz¹tku

dokumentacjê (avr300), po krót-

kim przyjrzeniu kod wyda siê zna-

jomy. W istocie jest to przepisany

na C umieszczony w dokumentacji

opis generowania niezbêdnego

opóŸnienia. Wprowadzone zosta³y

jedynie niewielkie poprawki dosto-

sowuj¹ce kod do poznanej do tej

pory czêœci jêzyka C. Pierwsza

linia nie powinna budziæ w¹tpli-

woœci - odpowiedni wzór pojawi³

siê w poprzednim œródtytule. Dal-

sz¹ czêœæ ³atwiej jest zrozumieæ,

analizuj¹c kod od koñca. Funkcja

i2c_hdelay bêdzie wywo³ywana wszêdzie tam

w programie, gdzie potrzeba opóŸnienia

po³ówkowego. Jest to funkcja rozwijalna w

miejscu wywo³ania. W pierwszym przypadku

(opóŸnienie mniejsze ni¿ jeden cykl) kompi-

lator powinien wszystkie jej wywo³ania pomi-

n¹æ. Zauwa¿, ¿e reszta przypadków zosta³a

zoptymalizowana

w taki sposób, ¿e

umieszczenie w

kodzie programu

odniesienia do

funkcji spowodu-

je dodanie tylko

"#

Programowanie

Elektronika dla Wszystkich

ABC... C

Kompilacja warunkowa

jako sposób na zwiêkszenie

uniwersalnoœci kodu

Na samym pocz¹tku przypominam, aby wyjaœniæ

wszelkie w¹tpliwoœci, ¿e wszystkie instrukcje zaczy-

naj¹ce siê znakiem hash (#) s¹ dyrektywami preproce-

sora. Niektóre z nich mog¹ w szczególnoœci odnosiæ

siê do kompilatora. Jednak w ¿adnym razie nie tworz¹

one czêœci programu wpisywanego w procesor. Nie

mijaj¹c siê daleko z prawd¹, mo¿na powiedzieæ, ¿e

dyrektywy te tworz¹ program dla naszego kompilato-

ra - pozwalaj¹ nam sterowaæ przebiegiem kompilacji.

#if, #elif, #else, #endif

Wymienione powy¿ej dyrektywy s¹ bardzo podobne

w swojej sk³adni do instrukcji warunkowej C. Podsta-

wowy przyk³ad wykorzystania pokazuje listing:

#if STALA<2

// Wersja 1 programu

#elif STALA<100

// Wersja 2 programu

#else

// Wersja 3 programu

#endif

Zestaw takich instrukcji umo¿liwia skompilowa-

nie ró¿nych wersji programu dla ró¿nych wartoœci sta-

³ych. Mo¿na wyobraziæ sobie takie napisanie funkcji

obs³ugi wyœwietlacza, aby mo¿liwe by³o prze³¹czenie

za pomoc¹ jednej sta³ej miêdzy trybami sterowania

wyœwietlaczem podpiêtym, tak jak na naszej p³ytce,

a podpiêtym do szyny danych. Konieczne by³oby

umieszczenie miêdzy dyrektywami #if, #elif, #endif

ró¿nych wersji funkcji niskiego poziomu.

Przy tworzeniu wyra¿eñ warunkowych dla dyrek-

tyw kompilatora mo¿emy korzystaæ z wszelkich ope-

ratorów C. Mo¿emy porównywaæ, mno¿yæ, dzieliæ,

dodawaæ... Oczywistym ograniczeniem jest to, ¿e ope-

rowaæ mo¿emy tylko na wartoœciach sta³ych - takich

które znane s¹ w czasie kompilacji.

#ifdef, #ifndef

W pewien sposób oddzielnymi dyrektywami s¹ tytu³o-

we #ifdef, #ifndef. Mieliœmy okazjê ju¿ dzisiaj z nich

korzystaæ. Okazuje siê, ¿e w instrukcjach kompilacji

warunkowej mamy mo¿liwoœæ sprawdzenia, czy dana

sta³a zosta³a zdefiniowana, podjêcia w zale¿noœci od

tego odpowiednich sta³ych. Definicja w stylu:

#define NIC

jest jak najbardziej prawid³owa (wszystkie wyst¹pie-

nia identyfikatora NIC w kodzie zostan¹ zamienione...

na nic - zostan¹ po prostu usuniête). Pamiêtaj o tym -

takie dzia³anie czasami siê przydaje.

Jednak to, na co chcê teraz zwróciæ uwagê, to fakt,

¿e dokonanie takiej (oraz ka¿dej innej) definicji sta³ej

mo¿e byæ wykryte za pomoc¹ instrukcji #ifdef i #ifndef.

Przy tworzeniu wyra¿eñ mo¿na korzystaæ ze s³owa

kluczowego defined. Zwraca ono TRUE, jeœli dana

sta³a jest ju¿ zdefiniowana. Tak wiêc #ifdef i #ifndef

mog¹ byæ zast¹pione przez, odpowiednio:

#if defined NIC

#if !defined NIC

S³owa defined mo¿na u¿ywaæ tak¿e w z³o¿onych

warunkach.

Jak zwykle - wszystko bêdzie stawaæ siê coraz jaœ-

niejsza przy wprowadzaniu tego w ¿ycie.

Listing 42 - opóŸnienie po³ówkowe w i2c.c

#define I2C_nhalf (F_CPU/I2C_SPEED/2)

// Funkcja d³u¿szych opóŸnieñ

#if I2C_nhalf < 3

// Nic

#elif I2C_nhalf < 8

static void

i2c_xdelay(

void

)

{

NOP();

}

#else

#define I2C_delayloops (1+(I2C_nhalf-8)/3)

#if I2C_delayloops > 255

#error Przyspiesz - bo sie nie wyrabiam ;)

#endif

static void

i2c_xdelay(

void

)

{

asm volatile

( \

„delayus8_loop%=: \n\t„

\

„dec %[ticks] \n\t„

\

„brne delayus8_loop%= \n\t„

\

: :[ticks]

„r„

(I2C_delayloops) );

}

#endif

//I2C_nhalf >= 3

// OpóŸnienia dla I2C

static inline void

i2c_hdelay(

void

)

{

#if I2C_nhalf < 1

return

;

#elif I2C_nhalf < 2

NOP();

#elif I2C_nhalf < 3

asm volatile

(

„rjmp exit%=\n\t„

„exit%=:\n\t„

::);

#else

i2c_xdelay();

#endif

}

Listing 41 - nag³ówki w i2c.c

#include <avr\io.h>

#include <inttypes.h>

#include „harddef.h„

#include „makra.h„

#include „i2c.h„

background image

jednego s³owa programu: dla kolejnych przy-

padków bêdzie to kolejno: nop, rjmp, rcall.

Dla opóŸnieñ z zakresu 3-8 cykli wywo³ywa-

na jest funkcja zawieraj¹ca tylko jedn¹

instrukcjê: nop. Wywo³anie podprogramu,

nop, powrót z podprogramu - daje to 5 cykli.

Rozwi¹zanie nie jest „super” dok³adne, ale te¿

„super” dok³adnoœci nie potrzebujemy. Zwra-

cam uwagê na to, ¿e instrukcja NOP() jest

konieczna - inaczej kompilator usunie ca³¹

funkcjê i jej wywo³ania. Dla opóŸnieñ d³u¿-

szych tworzona jest znana nam ju¿ pêtla - ten

fragment nie powinien sprawiaæ problemów.

Nowoœci¹ w tym przypadku jest jedynie

sprawdzenie, czy iloœæ powtórzeñ pêtli mo¿e

byæ wykonana. Jeœli nie - zg³aszana jest infor-

macja o b³êdzie.

Kod nie jest taki straszny. Przyjrzyj siê

zastosowaniu instrukcji warunkowych - z ele-

mentów tych bêdziemy korzystaæ coraz czêœ-

ciej, tworz¹c coraz bardziej uniwersalne

modu³y.

I

2

C - reszta funkcji

Na listingu 43 mo¿esz zobaczyæ funkcje

zmieniaj¹ce odpowiednio fizyczne wyprowa-

dzenia portów. Moglibyœmy siê bez nich

obejœæ - wpisuj¹c w programie bezpoœrednio

instrukcje odwo³uj¹ce siê do portów. By³oby

to niewygodne przede wszystkim ze wzglêdu

na liniê SDA - decyduj¹c siê na wewnêtrzne

podci¹ganie musimy siê liczyæ z koniecznoœ-

ci¹ bardziej skomplikowanego sterowania

portem - dla stanu wysokiego konieczne jest

ustawienie portu jako wejœcia i w³¹czenia

podci¹gania, dla stanu niskiego wy³¹cze-

nie podci¹gania i prze³¹czenie portu

w tryb wyjœcia. Wykonanie analogicznych

funkcji dla linii zegara zwiêksza czytel-

noϾ kodu.

Moglibyœmy w tym miejscu pos³u¿yæ

siê tak¿e makrami - tak jak robiliœmy to

w poprzednich czêœciach. Du¿o zale¿y od

preferencji. Mnie funkcje w tym miejscu

wydaj¹ siê bardziej przyjazne.

Serce obs³ugi I

2

C znajduje siê na listin-

gu 44. Przyjrzyj siê, w jaki sposób genero-

wane s¹ warunki START i STOP. W razie

potrzeby ps³u¿ siê rysunkami 28-31.

Generacja warunku start zosta³a troszkê

skomplikowana ze wzglêdu na mo¿liwoœæ

jego wywo³ania tak¿e bez koñczenia trans-

misji. Konieczne jest uwzglêdnienie faktu,

¿e funkcja wysy³aj¹ca i odbieraj¹ca bajt

zostawia zawsze liniê SCL w stanie nis-

kim, jednak stan linii SDA nie jest okreœ-

lony.

Poœród naszych funkcji nie ma takich,

które obs³ugiwa³yby kompletne ramki,

takie jak na rysunku 31. W najprostszym

przypadku nie s¹ one potrzebne - wys³anie

dowolnej ramki mo¿e byæ zrealizowane

przez wy¿sz¹ czêœæ programu za pomoc¹

czterech tylko funkcji z listingu 44.

Modu³ i2c zamyka wpisanie do pliku

nag³ówkowego zawartoœci listingu 45.

"$

Programowanie

Elektronika dla Wszystkich

i

{

}

Listing 43 - operacje na portach w i2c.c

// Ustawienie i zerowanie wyjœcia

static inline void

i2c_sdaset(

void

)

{

DDR(I2C_SDAPORT) &= ~(

1

<<I2C_SDA);

PORT(I2C_SDAPORT) |=

1

<<I2C_SDA;

}

static inline void

i2c_sdaclear(

void

)

{

PORT(I2C_SDAPORT) &= ~(

1

<<I2C_SDA);

DDR(I2C_SDAPORT) |=

1

<<I2C_SDA;

}

// Pobieranie danej z wyprowadzenia portu

static inline

uint8_t i2c_sdaget(

void

)

{

return

PIN(I2C_SDAPORT) & (

1

<<I2C_SDA);

}

// Zerowanie i ustawianie zegara

static inline void

i2c_sclset(

void

)

{

PORT(I2C_SCLPORT) |=

1

<<I2C_SCL;

}

static inline void

i2c_sclclear(

void

)

{

PORT(I2C_SCLPORT) &= ~(

1

<<I2C_SCL);

}

ABC... C

Generowanie w³asnych b³êdów i ostrze¿eñ

Gdy kompilator natknie siê na dyrektywê #error,

zakoñczy swoj¹ pracê wyœwietlaj¹c opis b³êdu który

podajemy zaraz za dyrektyw¹.

Aby wyjaœniæ sposób oraz ideê tworzenia w³as-

nych informacji o b³êdzie pos³u¿my siê troszkê

uproszczon¹ czêœci¹ kodu z listingu 42. Posiadamy

tutaj funkcjê opóŸnienia, która dzia³a prawid³owo

dla liczby o wartoœci najwy¿ej 255, próba podania

wartoœci wiêkszej spowoduje zignorowanie najstar-

szych bitów, o czym nie zostaniemy nawet poinfor-

mowani (jest to jedna z przykroœci wstawek asem-

blerowych - kompilator ich nie pilnuje). Jeœli opóŸ-

nienie jest wartoœci¹ sta³¹, mo¿emy za pomoc¹ pros-

tej konstrukcji z kompilacj¹ warunkow¹ (poprzednia

ramka) sprawdziæ czy zakres nie zosta³ przekroczo-

ny:

#define opoznienie 256

#if opoznienie > 255

#error

za duze opoznienie!

#endif

Oczywiœcie przedstawiony przyk³ad jest banalny -

zawsze uzyskamy informacjê o b³êdzie wygl¹daj¹c¹

mniej wiêcej jak poni¿ej:

i2c.c:21:3: #error za duze opoznienie!

Klikniêcie na powy¿szy tekst w okienku Output

spowoduje przeniesie kursora na pozycje, gdzie poja-

wi³a siê dyrektywa #error. Przytoczony przyk³ad,

oczywiœcie w praktyce, nie ma wiêkszego sensu. Jed-

nak, ju¿ jeœli wartoœæ sta³ej opoznienie bêdzie wyni-

kiem jakiœ obliczeñ, przeprowadzenie stosownego

sprawdzenia jest ca³kowicie uzasadnione.

Wa¿ne jest to, aby zrozumieæ, ¿e kompilator zg³o-

si b³¹d zawsze gdy natknie siê na dyrektywê #error. W

praktyce wiêc u¿ywamy jej ³¹cznie z dyrektywami

kompilacji warunkowej.

Jeœli rozumiesz ju¿ ideê komendy #error, nie

bêdziesz mia³ ¿adnych problemów z komend¹ #war-

ning. Jej dzia³anie jest bardzo zbli¿one. Ró¿ni siê

jedynie tym, ¿e po zg³oszeniu ostrze¿enia kompilacja

jest kontynuowana. Mo¿esz postanowiæ, ¿e b³¹d prze-

pe³nienia zakresu naszego opóŸnienia nie jest tak

wa¿ny aby zatrzymywaæ kompilacjê i pozostawiæ

jedynie generowanie ostrze¿enia. Nic prostszego:

wystarczy zamieniæ s³owo error na warning.

Listing 44 - serce i2c.c

void

i2c_start(

void

)

{

// Jeœli start bez stop

i2c_sdaset();

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

// Normalna sekwencja startu

i2c_sdaclear();

i2c_hdelay();

i2c_sclclear();

}

void

i2c_stop(

void

)

{

i2c_sdaclear();

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

i2c_sdaset();

i2c_hdelay();

}

uint8_t i2c_send(uint8_t data)

{

uint8_t n;

for

(n=

8

; n>

0

; —n)

{

if

(data &

0x80

)

i2c_sdaset();

else

i2c_sdaclear();

data <<=

1

;

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

i2c_sclclear();

}

// ack

i2c_sdaset();

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

n = i2c_sdaget();

i2c_sclclear();

return

n;

}

uint8_t i2c_get(uint8_t ack)

{

uint8_t n, temp=

0

;

i2c_sdaset();

for

(n=

8

; n>

0

; —n)

{

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

temp<<=

1

;

if

(i2c_sdaget())

temp++;

i2c_sclclear();

}

// ack

if

(ack == I2C_ACK)

i2c_sdaclear();

else

i2c_sdaset();

i2c_hdelay();

i2c_sclset();

i2c_hdelay();

i2c_sclclear();

return

temp;

}

Listing 45 - plik i2c.h

#ifndef I2C_H_INCLUDED

#define I2C_H_INCLUDED

#define I2C_ACK 1

#define I2C_NACK 0

void

i2c_start(

void

);

void

i2c_stop(

void

);

uint8_t i2c_send(uint8_t data);

uint8_t i2c_get(uint8_t ack);

#endif

//I2C_H_INCLUDED

background image

Plik g³ówny

Na tym etapie pracy mamy gotowe wszystkie

potrzebne nam modu³y. Pozostaje po³¹czyæ je

w dzia³aj¹cy program. Poniewa¿ zale¿y nam

na szybkim uruchomieniu programu, teraz

wszystkie niezbêdne funkcje wywo³amy z po-

ziomu funkcji main - tylko po to, aby wresz-

cie coœ siê zaczê³o dziaæ. Utwórz plik main.c.

Powinien pojawiæ siê listing 46. Wiêksza

czêœæ zawartego tutaj kodu powinna byæ ju¿ Ci

znana. Nowoœci¹ jest jedynie wysy³anie oraz

odbiór danych z uk³adu przetwornika. Na

rysunkach 32 i 33 zamieœci³em fragmenty

wyjête z dokumentacji firmy Philips. Pokazuj¹

one, jak powinna przebiegaæ komunikacja

z naszym uk³adem. Jak widaæ na rysunku 32,

zgodnie z opisanymi wczeœniej ramkami

transmisji, po podaniu warunku start,

konieczne jest zaadresowanie uk³adu. Adres

PCF8591 sk³ada siê z czterech starszych

bitów ustawionych na sta³e (1001b = 9h) oraz

trzech m³odszych, które mo¿emy wybraæ

poprzez odpowiednie zwarcie wyprowadzeñ

adresowych. P³ytka AVT3500 ustawia na

sta³e w tym miejscu 0. Najm³odszy bit s³owa

adresowego s³u¿y do ustalenia czy odbywa

siê zapis, czy odczyt. Przy zapisie, w pierw-

szej kolejnoœci wysy³any jest bajt konfigura-

cyjny. Jego znaczenie pokazuje rysunek 33.

Przyjrzyjmy siê programowi. Po starcie

transmisji wysy³amy bajt 90h - adres uk³adu,

zapis. Nastêpnie bajt konfiguracyjny (0)

- wy³¹czone wyjœcie przetwornika CA, kon-

figuracja wejœæ na cztery niezale¿ne obwody,

wy³¹czone automatyczne prze³¹czanie kana-

³ów dla ka¿dego odczytu, kana³ 0.

W pêtli g³ównej w³¹czamy transmisjê,

pobieramy bajt danych i wy³¹czamy trans-

misjê. Usuniêcie z pêtli warunku STOP nie

spowoduje b³êdnego dzia³ania uk³adu. PóŸ-

niej, gdy uda nam siê skompilowaæ program,

spróbuj przenieœæ inicjacjê odczytu poza pêtlê

a w pêtli pozostawiæ sam¹ instrukcjê odczytu

danych. Zgodnie z rysunkiem 32 takie dzia-

³anie jest mo¿liwe.

Wyœwietlanie danych wykorzystuje utwo-

rzon¹ dziœ funkcjê lcd_dec. Zauwa¿, ¿e zaraz

po niej wpisywane s¹ dodatkowo dwie spacje.

Poprawia to czytelnoœæ wyniku, jeœli wczeœ-

niej wyœwietlana by³a liczba o wiêkszej iloœci

cyfr - w takim przypadku na wyœwietlaczu

pozostan¹ stare cyfry i trzeba je usun¹æ.

Przedstawiony sposób jest najprostszym

z mo¿liwych i nie wszêdzie mo¿e byæ zasto-

sowany. Dobrze siê spisuje jeœli nie zamierza-

my pisaæ nic wiêcej w danej linii.

W pliku main.c napisaliœmy proœciutki

program, którego zadaniem jest wyœwietlanie

danej odpowiadaj¹cej napiêciu podanemu na

wejœcie I0 naszej p³ytki. Zanim przejdziemy

dalej, pod³¹cz na to wejœcie Ÿród³o regulowa-

nego napiêcia. Mo¿e byæ to zasilacz albo -

najwygodniej - zwyk³y potencjometr (1-100k)

z przeciwleg³ymi wyprowadzeniami wpiêtymi

miêdzy masê i zasilanie a œrodkowym do wej-

œcia I0.

Konfiguracja makefile

Gdybyœ chcia³ skompilowaæ teraz program,

czeka Ciê przykra niespodzianka - w tej chwi-

li nie jest to jeszcze mo¿liwe. Jeœli

zmieni³eœ, tak jak zwykle, pole

TARGET pliku makefile, otrzymasz

komunikat w stylu:

make.exe: *** No rule to make

target `PCF8591.o’, needed by

`PCF8591.elf’. Stop.

Nic dziwnego - nie istnieje prze-

cie¿ plik PCF8591.c. Skompilowa-

nie programu sk³adaj¹cego siê z

wiêkszej liczby plików wymagaæ

bêdzie niewielkiej modyfikacji

"%

Programowanie

Elektronika dla Wszystkich

ABC... C

Funkcje static i inline

Funkcja statyczna

Umieszczenie przed nazw¹ funkcji s³owa kluczowego

static powoduje utworzenie tak zwanej funkcji sta-

tycznej. Nie ma to wiele wspólnego z poznanymi ju¿

statycznymi zmiennymi lokalnymi. Tworz¹c funkcjê

statyczn¹, informujemy kompilator, ¿e bêdziemy

korzystaæ z niej tylko w bie¿¹cym module - co prak-

tycznie oznacza bie¿¹cy plik Ÿród³owy. Informacja

o danej funkcji nie zostanie umieszczona przez kom-

pilator w pliku obiektowym (*.o), przez co linkier nie

bêdzie mia³ do takiej funkcji dostêpu. Sam kompilator

uzyskuje za to mo¿liwoœæ lepszej optymalizacji kodu.

W skrajnym przypadku, jeœli funkcja nie zostanie

wykorzystana, nie znajdzie siê ona w kodzie wyniko-

wym programu (jednak zostanie w tym przypadku

wygenerowane ostrze¿enie).

Funkcji static u¿yjemy wszêdzie tam, gdzie pew-

nych elementów modu³u nie chcemy udostêpniaæ na

zewn¹trz. W naszym module wyœwietlacza mo¿na by

zastosowaæ to do wszystkich funkcji niskiego pozio-

mu.

Funkcja rozwijana w miejscu wywo³ania

Pod t¹ niezwykle brzmi¹c¹ nazw¹ kryje siê mecha-

nizm zbli¿ony do makr. Ma on jednak kilka zalet.

Przede wszystkim, wygodniej siê pisze funkcje ni¿

makro. Drug¹ spraw¹ jest to, ¿e kompilator mo¿e zba-

daæ, czy bardziej optymalne jest rozwiniêcie funkcji

na wzór makra, czy te¿ jej zwyk³e wywo³anie.

Dodatkowo jeœli tworzymy funkcjê, kompilator pil-

nuje, czy nasze argumenty s¹ prawid³owe i jeœli to

konieczne, dokonuje przekszta³cenia ich typów. Zauwa¿-

my, ¿e z jednej strony zmniejsza to mo¿liwoœæ pope³nie-

nia b³êdu, z drugiej jednak sprawia, ¿e w niektórych

zastosowaniach sprawdzaj¹ siê tylko makra. WeŸmy jako

przyk³ad makro obliczaj¹ce iloœæ elementów w tablicy:

#define ELEMS(p)\

(sizeof(p)/sizeof(p[0]))

Twór taki zupe³nie nie ma sensu, jeœli p mia³oby

okreœlony typ - tak jak ma to miejsce w funkcji. Nale-

¿y zdawaæ sobie sprawê tak¿e, ¿e nawet tak banalne

makro, jak obliczenie sumy dwóch argumentów:

#define DODAJ(a, b) (a+b)

zachowa siê inaczej jako funkcja inline: makro operu-

je na argumentach o oryginalnych typach; Funkcja

inline przekszta³ci je najpierw do typu identycznego

ze swoimi argumentami.

static inline - czemu?

Jeœli chcesz zastosowaæ funkcjê inline, spotka Ciê

jeszcze jedna niespodzianka. Kompilator musi za³o-

¿yæ, ¿e poza jej wykorzystaniem w bie¿¹cym pliku,

mog¹ pojawiæ siê zewnêtrzne do niej odwo³ania. Jeœli nie

zaznaczysz funkcji inline jako statycznej, oprócz tego, ¿e

jej kod bêdzie rozwiniêty w ka¿dym miejscu wywo³ania,

dodatkowo zostanie w pamiêci utworzona jej „zwyk-

³a” wersja. W naszym przypadku by³oby to marnowa-

nie zasobów - s³ówko static rozwi¹zuje ten problem.

Dziêki optymalizacji kodu, funkcja static inline

sk³adaj¹ca siê tylko z rozkazu powrotu (return) nie

zostanie w ogóle umieszczona w kodzie.

Listing 46 - plik main.c

#include <avr\io.h>

#include <stdio.h>

#include <inttypes.h>

#include <avr\pgmspace.h>

#include „harddef.h„

#include „delay.h„

#include „makra.h„

#include „i2c.h„

#include „lcd.h„

int

main(

void

)

{

// Inicjacja

PORTD =

1

<<I2C_SDA |

1

<<I2C_SCL;

DDRD =

1

<<I2C_SCL;

DDRB =

1

<<LCD_E |

1

<<LCD_RS |

0x0F

<<LCD_D4;

lcd_init();

// Koniec inicjacji

// Tekst informacyjny w górnej linii

lcd_str_P((prog_char*)PSTR(

„Dane z I0:„

));

lcd_command(LCDC_DDA |

64

);

// Konfiguracja przetwornika

i2c_start();

// bajt adresowy, zapis

i2c_send(

0x90

);

// bajt kontrolny

i2c_send(

0x00

);

i2c_stop();

// Pobieranie danych

for

(;;)

{

lcd_command(LCDC_DDA |

64

);

// Odczyt danych

i2c_start();

// bajt adresowy, odczyt

i2c_send(

0x91

);

// Pobranie i wyœwietlenie danej

lcd_dec(i2c_get(I2C_NACK));

// Przys³oniêcie reszty napisu

lcd_str_P((prog_char*)PSTR(

„ „

));

i2c_stop();

}

return

0

;

}

Rysunek 32 - sposób komunikacji z uk³adem

PCF8591 - wed³ug dokumentacji

background image

dodatkowego pola. Zajrzyj do ostatniej ju¿

dzisiaj ramki ABC... C. Znajdziesz tam

dok³adny opis, co nale¿y zrobiæ, aby program

zacz¹³ dzia³aæ.

Porz¹dkowanie projektu

Jeœli zgodnie z opisem dodawa³eœ kolejno

wszystkie pliki do projektu, panel po lewej

stronie okna Programmers Notepada bêdzie

coraz mniej czytelny. Jak podoba Ci siê spo-

sób organizacji widocznych w nim wpisów

przedstawiony na rysunku 34? Zapewniam

Ciê, ¿e znakomicie uprzyjemnia to pracê.

Mo¿esz tworzyæ „katalogi” w okienku projek-

tu, klikaj¹c prawym klawiszem na nazwie,

jaka zosta³a mu nadana. Wybierz „Add New

Folder”. Pliki mo¿esz przenosiæ przez ich

przeci¹gniêcie na ikonkê wybranego katalo-

gu. Aby przenieœæ plik z wnêtrza katalogu na

zewn¹trz, przenieœ jego nazwê na ikonê pro-

jektu. Tworzony katalog i wykonane przeno-

szenie nie ma nic wspól-

nego z katalogami na

dysku. Umo¿liwia jedy-

nie wirtualne zorganizo-

wanie plików w obrêbie

projektu. Fizycznie pliki

pozostan¹ na swoich

miejscach.

Rzeczywiste odwzo-

rowanie struktury katalo-

gów miêdzy dyskiem a

oknem projektu umo¿li-

wia „Magic Folder”. Nie

polecam Ci jednak jego

stosowania - wymaga³o-

by to dodatkowej inge-

rencji w plik makefile.

Podsumowanie

Jeœli uda³o Ci siê napisaæ na podstawie tego

tekstu program ca³kowicie samodzielnie

- bardzo mnie to cieszy. Jeœli jednak program

nie chce siê uruchomiæ - zawsze mo¿esz siêg-

n¹æ do plików udostêpnionych na stronie

internetowej Elportalu. Zdajê sobie sprawê

z tego, ¿e dla niektórych takie programowanie

wyda siê niepotrzebnie skomplikowane.

Celowo nie wprowadza³em go na pocz¹tku.

Proste programy mo¿esz pisaæ w jednym

pliku. Jednak im program wiêkszy, tym sen-

sowniejsze staje siê jego dzielenie. Znacznie

³atwiej zapanowaæ nad dwudziestoma plikami

po tysi¹c linii kodu ka¿dy ni¿ nad jednym pli-

kiem z dwudziestoma tysi¹cami linii kodu

(przyk³ad z ¿ycia wziêty).

Od dzisiaj bêdziemy dzieliæ program na

czêœci wszêdzie tam, gdzie ma to sens. Myœlê,

¿e o ile nie jesteœ jeszcze przekonany, za-

czniesz dostrzegaæ wkrótce coraz wiêcej zalet

takiego programowania. Spróbuj powróciæ do

programu za dwa dni i zobacz, czy jeszcze

pamiêtasz, gdzie znajduj¹ siê poszczególne

elementy kodu. Zajrzyj do programów, które

pisaliœmy poprzednio w pojedynczych pli-

kach.

Propozycje zmian

Nasz program mimo tego, ¿e jest coraz bar-

dziej rozwiniêty, temat g³ównego bohatera

dzisiejszego æwiczenia traktuje doœæ skróto-

wo. Jeœli chcesz poæwiczyæ swoj¹ znajomoœæ

C, proponujê Ci rozwiniêcie tej czêœci progra-

mu.

1. Podana w dokumentacji szybkoϾ transmi-

sji uk³adu PCF8591 to 100kbps. SprawdŸ co

siê stanie przy wy¿szych czêstotliwoœciach.

2. Zauwa¿, ¿e w programie nie sprawdzamy,

czy uk³ad odpowiedzia³ zg³oszeniem ACK.

Do badania poprawnoœci transmisji wyko-

rzystaj fakt, ¿e funkcja i2c_get zwraca war-

toœæ 0 tylko, jeœli potwierdzenie zosta³o ode-

brane.

3. Dopisz dwie funkcje do modu³u i2c, które

zajm¹ siê transmisj¹ pe³nych ramek.

PodpowiedŸ: wykorzystaj istniej¹ce funkcje

i2c_send oraz i2c_get. Nowe funkcje powin-

ny przyjmowaæ adres, pod jaki dokonaæ zapi-

su/odczytu, wskaŸnik na tablicê, do której

umieœciæ/z której pobraæ dane oraz informacjê

o iloœci danych do odczytu/zapisu. Pamiêtaj,

aby nie generowaæ potwierdzenia po ostatnim

odczytanym bajcie.

4. Spróbuj uruchomiæ przetwornik cyfrowo-

-analogowy. W tym celu konieczne jest prze-

s³anie kolejno danych 90h, 40h oraz danej,

która zostanie zapisana w rejestrze przetwor-

nika.

5. Mo¿e uda Ci siê napisaæ ca³y, nowy modu³

zajmuj¹cy siê transmisj¹ specjalnie do i z na-

szego przetwornika? Pamiêtaj, ¿e w takim

przypadku musisz zapamiêtaæ gdzieœ bajt

konfiguracji, tak aby by³o mo¿liwe jego

potwierdzanie przy ka¿dej zmianie wartoœci

wpisanej do przetwornika cyfrowo-analogo-

wego. A mo¿e funkcja zapisu wymaga³aby

podania konfiguracji przy wywo³aniu?

Rados³aw Koppel

radoslaw.koppel@edw.com.pl

"&

Programowanie

Elektronika dla Wszystkich

ABC... C

Przygotowanie pliku makefile

do kompilacji wielu plików Ÿród³owych

Potrzebna nam modyfikacja jest bardzo prosta.

W przysz³oœci powinieneœ wprowadzaæ j¹ jednoczeœ-

nie z dodawaniem nowego pliku Ÿród³owego do pro-

jektu... ale do rzeczy.

Otwórz w Programmers Notepadzie plik makefile

naszego projektu. Zaraz poni¿ej pola TARGET powi-

nieneœ znaleŸæ pole SRC. Na pocz¹tku powinno to

wygl¹daæ jak

w ramce obok:

Pole SRC

powinno za-

wieraæ pliki

Ÿród³owe pro-

jektu oddzielone spacjami. Domyœlnie w tym miejscu

pojawia siê nazwa projektu z rozszerzeniem *.c. Ma to

sens przy pisaniu ca³ego programu w jednym pliku

Ÿród³owym. Jednak w wiêkszych projektach staraj siê

raczej nie nadawaæ plikom Ÿróde³ nazw identycznych

z projektem - nie jest to b³¹d, ale mo¿e utrudniæ orien-

tacjê.

Kasujemy wiêc fragment ostatniej linii i zmienia-

my jej postaæ na nastêpuj¹c¹:

SRC

= main.c

SRC +

= i2c.c \

lcd.c \

delay.c

Po znakach ukoœników musi znaleŸæ siê znak

koñca linii. Wprowadzenie tutaj spacji spowoduje b³¹d

podczas kompilacji.

Ca³oœæ móg³byœ zapisaæ tak¿e w jednej linii.

Mo¿na te¿ przepisaæ wszystkie pliki od razu, bez

wykorzystywania operatora jak w drugiej linii. Jest to

kwestia gustu: przywyk³em do umieszczania na

pocz¹tku pliku g³ównego oraz wyraŸnego zaznacza-

nia, ¿e reszta plików zosta³a „dodana”. Kolejnoœæ wpi-

sania plików nie ma znaczenia. Jeœli gdziekolwiek

znajduje siê funkcja, od której program ma zacz¹æ -

linkier j¹ odnajdzie.

W tej chwili jesteœ gotowy do kompilacji pro-

gramu.

Uwa¿aj, nawet jeœli Twój system operacyjny nie

rozró¿nia wielkoœci znaków, dla WinAVR ma to zna-

czenie. Wpisz nazwy plików identyczne z tymi, jakie

zosta³y im nadane.

Rysunek 34
- porz¹dek
w projekcie

Rysunek 33 - konfiguracja PCF8591


Wyszukiwarka

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

więcej podobnych podstron