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 *.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 #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 #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 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