Prvi koraki pri uporabi C programskega jezika, razvojnega okolja Keil microvision, ter LPC 1343F procesorja.

Napisana vsebina je na začetku povzetek predavanja, ki ga je organiziralo Društvo Elektronikov, kot brezplačni seminar za svoje člane, ki bi se radi spoznali s ARM procesorji.
V nadaljevanju pa moje lastno prebijanje skozi dokumentacijo procesorja in C jezika s katerim sem se tule prvič resno srečal. Ne morem trditi, da nisem ustrelil kakšnega kozla a tako je če si samouk. :-)

Za prve korake bomo potrebovali:

Razvojno okolje Keil microvision, ki se lahko dobi tule: https://www.keil.com/demo/eval/arm.htm
Demo verzija ima sicer omejitve, kot so količina prevedene kode, manjka optimizacija in še nekaj drugih podrobnosti, ki pa na delo z LPC1343 nebi smeli vplivati. Namestitev programa se privzeto izvrši v mapo C:\Keil. V nasprotnem primeru bi bilo potrebno nastaviti kakšno stvar drugače, kot je opisano v tem postopku.
Procesor je prispajkan na TIV uLPC1000, ki je bil na voljo preko www.poscope.com - brez procesorja! Na voljo je tudi PDF z električno shemo ploščice.
Za prikop ploščice potrebujemo tudi USB kabel USB A na B.
Seveda potrebujemo tudi datasheet od našega procesorja: http://www.nxp.com/documents/user_manual/UM10375.pdf
Procesor LPC1343 vsebuje usb bootloader, ki omogoča, da se procesor računalniku predstavi kot USB ključek.

Postopek nalaganja programa na procesor:

Postopek izdelave novega projekta v delovnem okolju Keil uVision :

Koda:
#include <stdio.h>
#include "LPC13xx.h"
int main (void) {
while (1) {}
}
Tako, da imamo nekaj kar se lahko prevede.
Napisano pa se trenutno še ne prevede uspešno.
Projekt se sedaj uspešno prevede.
Ko je projekt uspešno preveden se v mapi Flash pojavi en kup datotek, med njimi pa je ena z imenom Projekt.bin, ki se jo lahko prekopira na procesor.

C in LPC1343

Tule ne bo detajlnega opisa C jezika in njegove strukture, le nekaj najosnovnejših ukazov in delov, ki so direktno vezani na uporabo C jezika pri programiranju LPC1343.
Pri pisanju programov je pametno svoje delo komentirati. To se lahko stori na dva načina:
Z uporabo znakov "//" - besedilo za znakoma // je komentar, ki se razteza samo čez eno vrstico programske kode.
Z uporabo para "/* */" - besedilo med znaki /* */ je komentar, ki se lahko razteza čez več vrstic programske kode.
Pri uporabi procesorja - predvsem pri pisanju programov se je potrebno s procesorjem kar intimno spoznati. Pisanje programov je drugače skoraj nemogoče, če ne poznamo ukazov s katerimi procesor prepričamo, da ob želenemu času ustrezno vključi ali izključi izhode in s tem opravi določeno nalogo.

Najosnovnejše operacije, ki jih mora procesor opravljati so prejemanje in pošiljanje podatkov preko svojih nožic v zunanji svet. Da je stvar bolj zamotana lahko posamezna nožica procesorja opravlja več funkcij. Da ustrezno deluje je potrebno procesor ustrezno nastaviti.

Procesor LPC 1343 ima 48 nožic.
Razporejene pa so v 4 splošne vhodno-izhodne (I/O)enote, ki se imenujejo PIO0, PIO1, PIO2, PIO3 in nekaj drugih delov. Posamezne I/O enote imajo različno število nožic: Posamezna nožica opravlja lahko različne funkcije. Vse funkcije so navedene na straneh 125-127 (Table 144) UM10375.pdf, hiter pregled pa je na voljo preko slike našega procesorja na strani 120 (Fig 10).

Če hočemo, da procesor na I/O enoto pošilja podatke, moramo enoto ustrezno nastaviti.
Delovanje posamezne I/O enote krmilimo z registri, ki se imenujejo GPIO0GPIO3.
Za posamezno I/O enoto imamo na razpolago več GPIO registrov, s katerimi nastavljamo različne funkcije za posamezen pin. Ni nujno, da so vse funkcije na voljo za vsak pin.
Preko posameznega GPIO registra lahko nastavimo obnašanje posameznega pina I/O enote.
Za GPIO0 so na voljo: ter drugi, ki nas v tem trenutku ne zanimajo.
Detajlni podatki o GPIO0 se nahajajo na strani 132 UM10375.pdf.

Nastavljanje bita v registru

GPIO0DATA – je register, ki določa kakšen podatek je vpisan na posamezen pin PIO0 I/O enote (1 ali 0). Če je posamezen pin nastavljen za vhod ali kakšno drugo funkcijo vrednost v tem registru ne vpliva nanj.
GPIO0DIR – je register, ki določa ali je posamezen pin PIO0 I/O enote vhod ali izhod. 0 je vhod, 1 je izhod.

Kako je pa to izvedeno v našem programu?
Žal v C jeziku ne moremo direktno nasloviti samo en bit informacije v nekem registru. Zato je potrebno nasloviti celoten register, kar pa zna biti problematično, če nočemo spreminjati določenih bitov. Zato je ukaz za pošiljanje bita (1) na PIO0 pin 8 takšen:
Koda:

LPC_GPIO0->DATA |= (1<<8);
Kako se pa to izvede?
(1<<8) pomeni, da se na najnižji bit začasnega registra vpiše 1 nato pa premakne za 8 mest v levo.

Tako dobimo:
0000 0000 0000 0001 -> vpišemo 1 na najnižji bit registra
0000 0001 0000 0000 -> premaknemo za 8 mest v levo

|= pomeni, da se dobljena vrednost v začasnem registru prišteje obstoječi vrednosti, ki je v registru, ki ga naslavljamo (GPIO0DATA). "|" je znak za binarni ali.
Če je vrednost v GPIO0DATA:
0000 0000 0000 0000
0000 0001 0000 0000 |
--------------------------
0000 0001 0000 0000 rezultat v registru GPIO0DATA
Opisani postopek torej spremeni le en bit v registru GPIO0DATA.

Brisanje bita v registru

Kako pa določen bit pobrisati?
Postopek je podoben, le nekaj črk je dodanih:
koda:
LPC_GPIO0->DATA &= ~(1<<8)
Znak ~ pomeni, da je vrednost invertirana tako iz podatka
0000 0001 0000 0000 dobimo
1111 1110 1111 1111

Tako dobimo, če je v GPIO0DATA:
1110 1001 0110 0000
1111 1110 1111 1111 &
-------------------------
1110 1000 0110 0000 rezultat v registru GPIO0DATA
Opisani postopek torej spremeni le en bit v registru GPIO0DATA
Ker pa samo pisanje podatkov v register, če I/O enota ni nastavljena kot izhod ne da nobenega vidnega učinka, je napisan tale preprost programček:
Koda:
#include <stdio.h> /* tole niti ni potrebno za ta primer */
#include "LPC13xx.h" /* LPC13xx definicije */
int main (void) { /* glavna zanka */
int i; /* spremenljivka i je tipa int */
LPC_GPIO0->DIR |= (1<<8); /* |= (OR) Vpišemo 1 v 8 bit registra DIR (LED=P0.8) Nastavitev smeri PortaP0(GPIO0) P0=izhod */
while (1) { /* Neskončna zanka*/
LPC_GPIO0->DATA &= ~(1<<8); /* &= (IN) Vpišemo 0 v 8 bit registra DATA prižge LED 2 */
for (i=0;i<1000000;i++);
LPC_GPIO0->DATA |= (1<<8); /* |= (OR) Vpišemo 1 v 8 bit registra DATA ugasne LED2 */
for (i=0;i<1000000;i++);
}
}

Kako programček deluje?
int main (void) {} je glavna programska zanka
"i" je definirana kot spremenljivka tipa integer (celo številčno število)
LPC_GPIO0->DIR |= (1<<8); Bit 8 registra GPIO0 je nastavljen kot izhod – je postavljen na 1. GPIO0.8 je v bistvu pin PIO0.8, na katerem je naša LED z oznako 2. - Glej shemo.pdf. Ker so LED diode vezane fiksno na VCC, se lahko prižgejo le tako, da se izhod procesorja postavi na 0.
while (1) {} je neskončna zanka, ki se izvaja dokler procesorja ne resetiramo.
LPC_GPIO0->DATA &= ~(1<<8); izhod PIO0.8 postavimo na 0 – LED se prižge
for (i=0;i<1000000;i++); preprost časovnik, ki je odvisen od hitrosti kristala procesorja. Kako zadeva deluje? Spremenljivko postavimo na 0 (i=0) v naslednjem koraku jo povečamo za 1 (i++) in to ponavljamo do 999999 (i<1000000) Procesor se vrti v krogu dokler ne prešteje 1000000 ciklov.
Ko konča skoči na naslednjo vrstico
LPC_GPIO0->DATA |= (1<<8); izhod PIO0.8 postavimo na 1 – LED se ugasne, sledi ponovno pavza
for (i=0;i<1000000;i++);
Dioda pri 12MHz kristalu procesorja in privzetih nastavitvah utripa s hitrostjo cca 4Hz

Iz kje sem pa potegnil ukaze LPC_GPIO0 in DATA ter DIR?
Zapisani so v datoteki LPC13xx.h, kjer so definirani ukazi, ki jih lahko uporabimo:
Začnejo se pri vrstici 547, nas pa zanima tale:
Koda:

#define LPC_GPIO0 ((LPC_GPIO_TypeDef *) LPC_GPIO0_BASE )
Ter seveda vrstica 320, kjer je pričetek definicije:
typedef struct za naš register LPC_GPIO definicija vsebuje dve besedi, ki nas zanimata: DIR in DATA.
Ostane le, da programček prevedemo in naložimo na procesor.

Tule so na voljo še arhivi s Keil projekti in električno shemo:
Električna shema tiskanine uLPC1000 shema.pdf
Keil projekt Blinky.zip
Arhiv s programčkom LPCCRC

Del 2: Define

Ukaz #define prevajalniku pove, da naj v kodi, ki jo prevaja določen izraz zamenja z drugim. Uporaba je enostavna in smiselna, saj bistveno poveča berljivost kode, in hkrati omogoča hitro spreminjanje parametrov, če je to potrebno.

Koda:
#include <stdio.h>
#include "LPC13xx.h" // LPC13xx definitions
int main (void) { // Main Program
int i; // spremenljivka i je tipa int
LPC_GPIO0->DIR |= (1<<8); // |= (OR) Vpišemo 1 v 8 bit registra DIR GPIO0.8, P0.8=izhod LED2
LPC_GPIO0->DIR |= (1<<9); // |= (OR) Vpišemo 1 v 9 bit registra DIR GPIO0.9, P0.9=izhod LED3
LPC_GPIO1->DIR |= (1<<10); // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO1.10, P1.10=izhod LED4
LPC_GPIO2->DIR |= (1<<2); // |= (OR) Vpišemo 1 v 2 bit registra DIR GPIO2.2, P2.2=izhod LED1
LPC_GPIO2->DIR |= (1<<10); // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO2.10, P2.10=izhod LED8
LPC_GPIO3->DIR |= (1<<0); // |= (OR) Vpišemo 1 v 0 bit registra DIR GPIO3.0, P3.0=izhod LED5

while (1) { // Loop forever

LPC_GPIO2->DATA |= (1<<2); // |= (OR) Vpiše 1 v 2 bitu registra DATA GPIO2.2 ugasne LED1
LPC_GPIO0->DATA ^= (1<<8); // ^= (XOR) Zamenja stanje v 8 bitu registra DATA GPIO0.8 prižge LED2
LPC_GPIO0->DATA |= (1<<9); // |= (OR) Vpiše 1 v 9 bit registra DATA GPIO0.9 ugasne LED3
LPC_GPIO1->DATA ^= (1<<10); // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO1.10 prižge LED4
LPC_GPIO3->DATA |= (1<<0); // |= (OR) Vpiše 1 v 0 bit registra DATA GPIO3.0 ugasne LED5
LPC_GPIO2->DATA ^= (1<<10); // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO2.10 prižge LED8
for (i=0;i<1000000;i++);
LPC_GPIO2->DATA ^= (1<<2); // ^= (XOR) Zamenja stanje v 2 bitu registra DATA GPIO2.2 prižge LED1
LPC_GPIO0->DATA |= (1<<8); // |= (OR) Vpiše 1 v 8 bit registra DATA GPIO0.8 ugasne LED2
LPC_GPIO0->DATA ^= (1<<9); // ^= (XOR) Zamenja stanje v 9 bitu registra DATA GPIO0.9 prižge LED3
LPC_GPIO1->DATA |= (1<<10); // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO1.10 ugasne LED4
LPC_GPIO3->DATA ^= (1<<0); // ^= (XOR) Zamenja stanje v 0 bitu registra DATA GPIO3.0 prižge LED5
LPC_GPIO2->DATA |= (1<<10); // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO2.10 ugasne LED8
for (i=0;i<1000000;i++);
}
}
Spodaj pa je koda, ki je prevedena identična. Le izgleda precej drugače.
Koda:
#include <stdio.h>
#include "LPC13xx.h" //LPC13xx definitions
#define LED2 (1<<8) //P0.8
#define LED3 (1<<9 // P0.9
#define LED4 (1<<10) // P1.10
#define LED1 (1<<2) // P2.2
#define LED8 (1<<10 // P2.10
#define LED5 (1<<0) // P3.0
#define zakasnitev 1000000 // številka, ki določa hitrost utripanja

int main (void) { // Main Program
int i; // spremenljivka i je tipa int
LPC_GPIO0->DIR |= LED2; // |= (OR) Vpišemo 1 v 8 bit registra DIR GPIO0.8, P0.8=izhod LED2
LPC_GPIO0->DIR |= LED3; // |= (OR) Vpišemo 1 v 9 bit registra DIR GPIO0.9, P0.9=izhod LED3
LPC_GPIO1->DIR |= LED4; // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO1.10, P1.10=izhod LED4
LPC_GPIO2->DIR |= LED1; // |= (OR) Vpišemo 1 v 2 bit registra DIR GPIO2.2, P2.2=izhod LED1
LPC_GPIO2->DIR |= LED8; // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO2.10, P2.10=izhod LED8
LPC_GPIO3->DIR |= LED5; // |= (OR) Vpišemo 1 v 0 bit registra DIR GPIO3.0,P3.0=izhod LED5

while (1) { // Loop forever tule se izvaja naš program

LPC_GPIO2->DATA |= LED1; // |= (OR) Vpiše 1 v 2 bitu registra DATA GPIO2.2 ugasne LED1
LPC_GPIO0->DATA ^= LED2; // ^= (XOR) Zamenja stanje v 8 bitu registra DATA GPIO0.8 prižge LED2
LPC_GPIO0->DATA |= LED3; // |= (OR) Vpiše 1 v 9 bit registra DATA GPIO0.9 ugasne LED3
LPC_GPIO1->DATA ^= LED4; // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO1.10 prižge LED4
LPC_GPIO3->DATA |= LED5; // |= (OR) Vpiše 1 v 0 bit registra DATA GPIO3.0 ugasne LED5
LPC_GPIO2->DATA ^= LED8; // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO2.10 prižge LED8
for (i=0;i<zakasnitev;i++);
LPC_GPIO2->DATA ^= LED1; // ^= (XOR) Zamenja stanje v 2 bitu registra DATA GPIO2.2 prižge LED1
LPC_GPIO0->DATA |= LED2; // |= (OR) Vpiše 1 v 8 bit registra DATA GPIO0.8 ugasne LED2
LPC_GPIO0->DATA ^= LED3; // ^= (XOR) Zamenja stanje v 9 bitu registra DATA GPIO0.9 prižge LED3
LPC_GPIO1->DATA |= LED4; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO1.10 ugasne LED4
LPC_GPIO3->DATA ^= LED5; // ^= (XOR) Zamenja stanje v 0 bitu registra DATA GPIO3.0 prižge LED5
LPC_GPIO2->DATA |= LED8; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO2.10 ugasne LED8
for (i=0;i<zakasnitev;i++);
}
}

Bistvena novost so spodnji stavki:
Koda:

#define LED2 (1<<8) // P0.8
#define LED3 (1<<9) // P0.9
#define LED4 (1<<10) // P1.10
#define LED1 (1<<2) // P2.2
#define LED8 (1<<10) // P2.10
#define LED5 (1<<0) // P3.0
#define zakasnitev 1000000 // številka, ki določa hitrost utripanja
Z njimi priredimo kriptičnim ukazom bolj berljivo obliko, ki jo lahko uporabimo skozi celotno našo kodo. Če LED 2 prestavimo na drugo mesto, je potrebno ukaz spremeniti samo na enem mestu.
Pri kratki kodi, kot je dani primer je to relativno enostavno, ko pa se koda napihne čez več strani, pa pregledovanje postane hudo težavno v kolikor si ne pomagamo s takšnimi triki.

Tule sta še arhiva projektov:
Define1.zip
Define2.zip

Del 3: Uporaba tipk

Naša razvojna ploščica že ima prispajkani 2 tipki. Prva z imenom RESET je vezana preko nekaj elementov na reset pin procesorja P0.0, druga z imenom INT pa na P0.1 – glej shemo.pdf.
Ker pritisk na RESET tipko dejansko resetira procesor, za naš namen ni preveč uporabna. Za test smo tako uporabili tipko INT. Če hočemo uporabiti tipko mora biti pin, na katerega je vezana definiran kot vhod. Toda koda v našem programu (glej spodaj) tega nikjer eksplicitno ne navaja.
Zakaj? Procesor ob zagonu pine postavi na določene funkcije. Kakšno nalogo bo določen pin ob zagonu opravljal si lahko pogledate v datoteki um10375.pdf od strani 124 naprej. Za pin, ki nas zanima P0.1 piše, da je ob zagonu vhod. Zato posebna konfiguracija ni potrebna.

Če pa bi pin želeli vseeno nastaviti za vhod pa to lahko storimo z ukazom:
Koda:
LPC_GPIO0->DIR &= ~tipka // &=(AND) ~ = invertirana vrednost Vpišemo 0 v 1 bit registra DIR GPIO.1 P0.1=vhod tipka
Koda:
#include <stdio.h>
#include "LPC13xx.h" //LPC13xx definitions
#define
LED2 (1<<8) // P0.8
#define LED3 (1<<9) // P0.9
#define LED4 (1<<10) // P1.10
#define LED1 (1<<2) // P2.2
#define LED8 (1<<10) // P2.10
#define LED5 (1<<0) // P3.0
#define zakasnitev 1000000 // številka, ki določa hitrost utripanja
#define tipka (1<<1) // Tipka INT je vezana na P0.1
int main (void) // Main Program
{
int i; // spremenljivka i je tipa int
LPC_GPIO0->DIR |= LED2; // |= (OR) Vpišemo 1 v 8 bit registra DIR GPIO0.8, P0.8=izhod LED2
LPC_GPIO0->DIR |= LED3; // |= (OR) Vpišemo 1 v 9 bit registra DIR GPIO0.9, P0.9=izhod LED3
LPC_GPIO1->DIR |= LED4; // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO1.10, P1.10=izhod LED4
LPC_GPIO2->DIR |= LED1; // |= (OR) Vpišemo 1 v 2 bit registra DIR GPIO2.2, P2.2=izhod LED1
LPC_GPIO2->DIR |= LED8; // |= (OR) Vpišemo 1 v 10 bit registra DIR GPIO2.10, P2.10=izhod LED8
LPC_GPIO3->DIR |= LED5; // |= (OR) Vpišemo 1 v 0 bit registra DIR GPIO3.0, P3.0=izhod LED5

while (1) // Loop forever tule se izvaja naš program
{
if (!(LPC_GPIO0->DATA & tipka))
{
LPC_GPIO2->DATA |= LED1; // |= (OR) Vpiše 1 v 2 bitu registra DATA GPIO2.2 ugasne LED1
LPC_GPIO0->DATA ^= LED2; // ^= (XOR) Zamenja stanje v 8 bitu registra DATA GPIO0.8 prižge LED2
LPC_GPIO0->DATA |= LED3; // |= (OR) Vpiše 1 v 9 bit registra DATA GPIO0.9 ugasne LED3
LPC_GPIO1->DATA ^= LED4; // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO1.10 prižge LED4
LPC_GPIO3->DATA |= LED5; // |= (OR) Vpiše 1 v 0 bit registra DATA GPIO3.0 ugasne LED5
LPC_GPIO2->DATA ^= LED8; // ^= (XOR) Zamenja stanje v 10 bitu registra DATA GPIO2.10 prižge LED8
for (i=0;i<zakasnitev;i++);
LPC_GPIO2->DATA ^= LED1; // ^= (XOR) Zamenja stanje v 2 bitu registra DATA GPIO2.2 prižge LED1
LPC_GPIO0->DATA |= LED2; // |= (OR) Vpiše 1 v 8 bit registra DATA GPIO0.8 ugasne LED2
LPC_GPIO0->DATA ^= LED3; // ^= (XOR) Zamenja stanje v 9 bitu registra DATA GPIO0.9 prižge LED3
LPC_GPIO1->DATA |= LED4; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO1.10 ugasne LED4
LPC_GPIO3->DATA ^= LED5; // ^= (XOR) Zamenja stanje v 0 bitu registra DATA GPIO3.0 prižge LED5
LPC_GPIO2->DATA |= LED8; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO2.10 ugasne LED8
for (i=0;i<zakasnitev;i++);
}
else
{
LPC_GPIO2->DATA |= LED1; // |= (OR) Vpiše 1 v 2 bitu registra DATA GPIO2.2 ugasne LED1
LPC_GPIO0->DATA |= LED2; // |= (OR) Vpiše 1 v 8 bit registra DATA GPIO0.8 ugasne LED2
LPC_GPIO0->DATA |= LED3; // |= (OR) Vpiše 1 v 9 bit registra DATA GPIO0.9 ugasne LED3
LPC_GPIO1->DATA |= LED4; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO1.10 ugasne LED4
LPC_GPIO3->DATA |= LED5; // |= (OR) Vpiše 1 v 0 bit registra DATA GPIO3.0 ugasne LED5
LPC_GPIO2->DATA |= LED8; // |= (OR) Vpiše 1 v 10 bit registra DATA GPIO2.10 ugasne LED8
}
}
}
V kodi se je znašel nov ukaz if, za njim pa (!(LPC_GPIO0->DATA & tipka))
Ukaz if je pogojni stavek.
Struktura je takšna:

if (pogoj) {stavek 1 - izvede se, če je pogoj true}
else {stavek 2 - izvede se če je pogoj false}

Ni pa nujno, da napišemo celoten stavek
Lako uporabimo samo del:
if (pogoj) {stavek 1 – izvede se, če je pogoj true}
Torej v naši kodi pogoj if preverja ali je izraz (!(LPC_GPIO0->DATA & tipka)) enak true
LPC_GPIO0->DATA je register z vsebino, ki vsebuje tudi stanje pina P0.1. tipka je definicija lokacije naše tipke (1<<1) - P0.1
Torej z ukazom LPC_GPIO0->DATA & tipka procesor binarno sešteje vrednost podatkov v registru GPIO0 DATA in podatkom o lokaciji, ki nas zanima.
Tipka je preko upora vezana na +3,3V, kar pomeni, da je privzeto (ko tipka ni pritisnjena) na vhodu P0.1=1. Ko pritisnemo tipko – le ta potegne vhod na 0V – na vhodu dobimo 0.
Zato je pred izrazom še ť!Ť, ki celoten izraz negira.
Izraz bi se torej bralo takole:
če je P0.1=0
{
izvedi stavek 1
}
else
{
izvedi stavek 2
}
Kaj pa program dela? Ob pritisku izmenično utripajo LED, ki so na razpolago na ploščici, ko tipko spustimo LED diode ugasnejo.
Še arhiv do Keil projekta:
Uporaba_tipka.zip

Del 4: Serijska komunikacija

Prej ali slej pridemo do točke, ko je potrebno s procesorjem komunicirati s kakšno drugo napravo. Ena izmed enostavnejših in hkrati precej razširjenih zmožnosti je serijska komunikacija. Pri tem seveda mislim na serijsko komunikacijo, ki je zelo podobna RS232 komunikaciji. V čem so razlike? predvsem v napetostnih nivojih in dodatnih kontrolnih signalih, saj se tule vse skupaj dogaja le na RX in TX signalnih vodih.
Za enostavno komunikacijo, ki omogoča prenos posameznih znakov, so za nas pri keilu že spisali preprosto knjižnico serial.c, ki jo je preko datoteke glave serial.h kličemo iz našega programa.
Kaj pa vsebuje serial.h?
Tole:
extern void SER_init (void);
extern int sendchar (int c);
extern int chkkey (void);
extern int getkey (void);
Vsebuje torej definicije funkcij, ki ji lahko kličemo iz našega programa: Procesor LPC1343 ima vgrajen medpomnilnik dolžine 16 bajtov za pošiljanje in sprejemanje.
Opise posameznih funkcij lahko preberemo v serial.c, ki mora biti pred poizkusom prevajanja vključena v naš projekt.
Takole na hitro:
SER_init
Vsebuje nastavitve našega serijskega porta. Najbolj zanimive so naslednje vrstice:
LPC_IOCON->PIO1_6 = (1UL << 0); /* P1.6 is RxD */
LPC_IOCON->PIO1_7 = (1UL << 0); /* P1.7 is TxD */
LPC_UART->LCR = 0x83; /* 8 bits, no Parity, 1 Stop bit */
LPC_UART->DLL = 4; /* 115200 Baud Rate @ 12.0 MHZ PCLK */
LPC_UART->FDR = 0x85; /* FR 1.627, DIVADDVAL 5, MULVAL 8 */
Z njimi nastavimo RX in TX pin , lastnosti prenosa, ter hitrost prenosa.
Pa razložimo še nekaj podrobnosti:
P1.6 in P1.7 Sta uporabljena za serijsko komunikacijo zato, ker imata to funkcijo podprto že s strojno opremo. Da pa ju lahko uporabimo ju je potrebno ustrezno nastaviti.
Registru LPC_IOCON->PIO1_6 nastavimo prvi bit na 1 kako?

To stori ukaz (1UL <<0)
Register IOCON_PIO1_6 je dolg 32 bitov. Zato je uporabljen zapis 1UL kar pomeni, da je številka 1 zapisana na način unsigned long int.
Long int 1L ima lahko vrednost med -65536 ter +65536. (-2^16 do +2^16)
Unsigned long int 1UL pa ima lahko vrednost samo v pozitivno smer, zato vrednost po bitih enako dolga vendar samo v eno smer 2^32. Ker z ukazom (1UL<<0) premaknemo 1 za nič bitov v levo dobimo takšno številko:
XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXX1 v binarnem zapisu, X so seveda lahko poljubne vrednosti (1 ali 0). Če sedaj pogledamo v datasheet procesorja UM10375.pdf stran 115 piše 0x1 Selects function RXD – Pin 1.6 smo nastavili za funkcijo RX.

Sledi nastavljanje parametrov delovanja naše povezave:
LPC_UART->LCR = 0x83; /* 8 bits, no Parity, 1 Stop bit */
Registru U0LCR, ki je naslovljen z ukazom LPC_UART->LCR priredimo vrednost 0x83, kar v binarnem zapisu znaša:
0000 0000 1000 0011

Če pogledamo datasheet procesorja UM10375.pdf stran 189 stran lahko razberemo kaj posamezni biti pomenijo.
Prva dva bita določata dolžino znaka – 0x3 = b11 = 8 bitni znaki
Tretji bit določa ali bo stop bit dolžine 1 ali 2 bita. 0 = b0 = 1 stop bit
Četrti bit določa ali se uporablja paritetni bit 0 = b0 = ni paritetnega bita
Peti in šesti bit določata sodost /lihost paritetnega bita 0x0 = b00 = liha pariteta
Sedmi bit določa kontrolo preloma BC 0 = b0 = ni BC kontrole
Osmi bit omogoča uporabo delilnikov hitrosti 1 = b1 = je vključeno – omogoča spreminjanje hitrosti

Spreminjanje drugih parametrov pa je kar kompleksno, saj zahteva kar nekaj računanja.
Postopek za nekaj primerov je opisan v datasheetu procesorja UM10375.pdf na straneh 200-202.

Nekaj osnov smo tako spoznali. Sedaj je treba prikazano še uporabiti.
Za to je spisan programček blinky.c:
Koda:
#include <stdio.h>
#include "LPC13xx.h" // LPC13xx definitions
#include "serial.h" // preprosta knjižnica za serijsko komunikacijo
#define LED2 (1<<8) // P0.8
#define LED3 (1<<9) // P0.9
#define TIPKA (1<<1) // Tipka INT je vezana na P0.1
int main (void) { // Main Program

int i; // spremenljivka i je tipa int
int ch; // spremenljivka ch je tipa int
LPC_GPIO0->DIR |= LED2; // |= (ALI) Vpišemo 1 v 8 bit registra DIR GPIO0.8, P0.8=izhod LED2
LPC_GPIO0->DIR |= LED3; // |= (ALI) Vpišemo 1 v 9 bit registra DIR GPIO0.9, P0.9=izhod LED3
SER_init(); // Inicializacija serijskega porta
while (1) { // Neskončna zanka
if (!(LPC_GPIO0->DATA & TIPKA))// Če je pritisnjena tipka se izvede naslednja vrstica
{
sendchar ('c');
}
if (chkkey()) // če je prispel znak
{
ch=getkey();
if (ch=='c') // ali je prispeli znak c?
{
LPC_GPIO0->DATA ^= LED2 | LED3; // ^= (XOR) Spremenimo če je bila prej 1 v 0 in obratno diodi LED2 in LED3
for (i=0;i<1000000;i++);
}
}
}
}
Kako zadeva deluje?
Da programček je najenostavneje na naši testni ploščici povezati RX in TX pina. To storimo tako, da povežemo pina P1.6 in P1.7 našega procesorja. Na ploščici to zgleda takole:


To pa seveda ni nujno. V kolikor imamo pri roki kakšen USB-serijski vmesnik (sam sem uporabil kar tega: http://www.poscope.com/usb.html) lahko RX, TX ter GND povezave naredimo preko takšnega vmesnika in odziv spremljamo preko terminala na osebnem računalniku. Pri vsem tem je potrebno paziti, da signali niso previsoki. Načeloma serijska komunikacija procesorja dela na 3,3V, lahko do 5V, medtem, ko so napetostni nivoji običajne RS232 serijske komunikacije višji, kar lahko povzroči uničenje procesorja.

Na osebnem računalniku potrebujemo za potrebne serijske komunikacije terminalski programček. Okna imajo že nameščen program Terminal, ki za osnovno rabo zadošča, sam pa sem za poizkuse namestil kar programček Bray, katerega avtor je tudi član foruma Elektronik.si. Najdete ga lahko tule: https://sites.google.com/site/terminalbpp/

Kako pa lahko pošiljamo znak, če ima funkcija definirano vhodno spremenljivko INT?
Preprosto. Znaki so v bistvu številke. Če pogledamo ASCII tabelo znakov - ena je na vpogled tule: http://www.asciitable.com/ vidimo, da je posameznemu znaku določena številka, ki ima dolžino enega bajta. Kar pa je INT številka. Seveda naši šumniki nekako izpadejo iz tega seznama. Za to se uporablja različne kodne tabele, kjer so posamezni znaki opisani z večimi bajti podatkov.

Še arhiv s projektom:
Serijski_vmesnik.zip

Del 5: Uporaba PWM

Za nastavitev PWM delovanja potrebujemo 2 parametra. Ena je frekvenca signala PWM, drug pa razmerje med pavzo in signalom.

Pri uporabi PWM funkcij gre v bistvu za uporabo časovnikov. Naš procesor ima na voljo več časovnikov, ki zmorejo poganjati PWM. Na razpolago sta 2 16 bitna časovnika: TMR16B0 ter TMR16B1, ter 2 32 bitna časovnika TMR32B0 ter TMR32B1.

Uporaba 16 ali 32 bitnih časovnikov je praktično enaka, le registri imajo drugačno ime.
V našem primeru bomo uporabili izhoda PIO0_8 in PIO0_9, ki oba uporabljata register CT16B0 in s tem časovnik TMR16B0.

Za začetek je potrebno časovnike omogočiti. (Procesor ima večino funkcij izklopljenih zato, da zmanjša porabo)
To se stori z ukazom:
LPC_SYSCON->SYSAHBCLKCTRL s katerim v register AHB v bite 7-10 vpišemo katere časovnike hočemo vključiti (glej stran 25 datoteke UM10375.pdf)

Nato moramo naš naš časovnik nastaviti, da bo deloval v PWM načinu.
Temu služi ukaz LPC_TMR16B0->PWMC s katerim v register TMR16B0 vpišemo kateri kanali časovnika so namenjeni PWM in s tem vključimo PWM prožilcev, ki so določeni posameznim izhodnim pinom – Oznaka MATx. Če vpišemo v prvi bit registra TMR16B0PWMC 1. se vključi PWM za register MAT0, ker je funkcija CT16B0_MAT0 povezana s pinom PIO0_8 in pin je PWM izhod.

Pini, ki jih lahko priključimo na timerje za PWM so naslednji:
(Glej shemo procesorja na strani 120 datoteke UM10375.pdf )
PIO0_8 na CT16B0_MAT0
PIO0_9 na CT16B0_MAT1
PIO0_10 na CT16B0_MAT2
PIO1_9 na CT16B1_MAT0
PIO1_10 na CT16B1_MAT1
PIO1_1 na CT32B1_MAT0
PIO1_2 na CT32B1_MAT1
PIO1_3 na CT32B1_MAT2
PIO1_4 na CT32B1_MAT3
PIO1_6 na CT32B0_MAT0
PIO1_7 na CT32B0_MAT1
PIO0_1 na CT32B0_MAT2
PIO0_11 na CT32B0_MAT3
Kot vidimo so na posamezen časovnik vezani do 4 izhodi. To pomeni, da lahko z enim časovnikom kmilimo do 4 PWM signale, pri čemer pa je jasno, da en časovnik omogoča eno skupno frekvenco signala vsem izhodom hkrati, širina pulzov pa se lahko nastavi za vsak izhod posebej.

Sledi nastavljanje izhodov, da bodo omogočali uporabo PWM. Za to uporabimo za PIO0_9 ukaz:
Koda:
LPC_IOCON->PIO0_9 |= 0x02;
Z vpisom 0x02 v register IOCON_PIO0_9 izberemo način dela PIO0_9 izhoda, v našem primeru je to funkcija CT16B0_MAT1, ki je povezana s časovnikom TMR16B0.
(glej stran 104 datoteke UM10375.pdf)

Nastaviti moramo še parametre PWM.
Spodnji ukaz nastavi frekvenco našega PWM signala
Koda:
LPC_TMR16B0->MR3 = 28798;
Ukaz vpiše vrednost 28798 v MR3 register časovnika TMR16B0, da časovnik ve kateri signali skrbijo za njegovo proženje je uporabljen naslednji ukaz:

LPC_TMR16B0->MCR |= (1 << 10);
Ukaz vpiše 1 v 10 bit registra MCR.
Razlaga je na strani 272 datoteke UM10375.pdf) Ob nastavitvi 10 bita se vključi zastavica MR3R, ki resetira števec časovnika (TC) ko števec prišteje do vrednosti, ki je vpisana v MR3 register.
Kaj to pomeni? Ko se števec vključi, prične povečevati vrednost TC. Hitrost povečevanja je v našem primeru (glede na nastavitve v prvem delu tega poglavja) enaka hitrosti procesorja torej 72000000 x na sekundo. (72MHz).
Ko je vrednost v TC enaka MR3 se vrednost TC resetira in števec prične šteti od začetka.

Če malo poračunamo:
Čas 1 periode pri 72MHz je 13,888 e-9 s
MR3 je nastavljen na 28798. Torej en cikel štetja traja 28798 x 13,888e-9 = 0,4e-3s.
Če to pretvorimo v frekvenco znese 2500.1766Hz, kar je precej podobno 2,5KHz.
To je nosilna frekvenca našega PWM signala.

Kaj pa širina signala?
Naš PIO0_9 izhod uporablja MAT1 register, za identifikacijo.
Spodnji ukaz nastavi širino pulza na PIO0_9
LPC_TMR16B0->MR1 = 7200;
Ukaz vpiše vrednost 7200 v MR1 register.

Vrednost MR1 mora biti v območju med 0 in vrednostjo MR3. Njegova velikost pa določa širino pulza. Če MR1 izberemo za polovico MR3 bo imel naš signal razmerje 1/0 ravno 50%.
Manjša številka pomeni, da bo razmerje 1/0 manjše -vrednost 7200 je nekje 25%. To pomeni, da bo 1 dolžine 25%, 0 pa 75%.
Zaradi priklopa LED diod na vezju se pa to odraža ravno obratno. Pri daljši 0 diode dalj časa svetijo in so zato svetlejše.

Seveda je potrebno časovnik tudi omogočiti z ukazom:
Koda:
LPC_TMR16B0->TCR = 1;
(stran 271 datoteke UM10375.pdf)
Končen rezultat je potem programček, ki krmili 2 LED diodi vezani na PIO0_8 in PIO0_9, ki utripata s hitrostjo 2500 x na sekundo svetita pa 25 oziroma 75%.
Koda:
#include <stdio.h>
#include "LPC13xx.h" // LPC13xx definitions
#define LED2 (1<<8) // P0.8 LED2
#define LED3 (1<<9) // P0.9 LED3
int main (void) { // Main Program
LPC_GPIO0->DIR |= LED2 | LED3; // |= (ALI) Vpišemo 1 v 8 bit registra DIR (LED=P0.8, P0.9) izhodi
// Nastavitev smeri PortaP0(GPIO0) je izhod
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7); // vključi uro za CT16B0 register
LPC_SYSCON->SYSAHBCLKCTRL |= ((1UL << 6)| // vključi uro za GPIO
(1UL << 16)); // vključi uro za IOCON
LPC_IOCON->PIO0_9 |= 0x02; // 0000 0010 - PIO0_9 je priključen na CT16B0_MAT1 register
LPC_IOCON->PIO0_8 |= 0x02; // 0000 0010 - PIO0_8 je priključen na CT16B0_MAT0 register
LPC_TMR16B0->MCR |= (1 << 10); // 0000 0100 0000 0000 MR3R=1 - TC resetiran ko TC doseže vrednost MR3
LPC_TMR16B0->MR0 = 21598; // duty cycle 21598=75%, 7200=25% pri frekvenci 2,5KHz za PIO0_8 LED2
LPC_TMR16B0->MR1 = 7200; // duty cycle 14399=50%, 7200=25% pri frekvenci 2,5KHz za PIO0_9 LED3
LPC_TMR16B0->MR3 = 28798; // frekvenca 28798=2,5KHz, 14399=5KHz
LPC_TMR16B0->PWMC |= (1<<0); // vključi PWM za CT16Bn MAT0 PIO0_8
LPC_TMR16B0->PWMC |= (1<<1); // vključi PWM za CT16Bn MAT1 PIO0_9
LPC_TMR16B0->TCR = 1; // TC + PC enabled
while (1) { // Loop forever

}
Še nekaj, malo sem se igral tudi z nastavitvami in nosilna frekvenca PWM gre nekje do 2MHz. Potem pa postane verjetno zaradi kapacitivnosti? preveč občutljiva. LED diode sicer še vedno svetijo, ko pa se s sondo osciloskopa diode dotaknemo pa dioda kar pomežikne.

Spodaj je povezava na Keil projekt:
PWM.zip

Del 6 Ura

Procesor LPC 1343F ima vgrajen kompleksen časovnik, ki omogoča različne funkcije od merjenja časa do pulzno širinske modulacije. Da pa bi razumeli kako zadeva deluje je najprej potrebno pogledati na kakšni frekvenci deluje naš procesor.

Procesor ima na tiskanem vezju prispajkan 12MHz kvarčni kristal, vendar to ne pomeni da deluje na 12MHz. Procesor ima vgrajen zmogljiv tokovno krmiljen PLL oscilator, ki ga je potrebno ustrezno nastaviti. Vhodno frekvenco je potrebno v PLL oscilatorju dvigniti na 156-320MHz in nato zmanjšati na želeno vrednost.

Nastavitve za izbiro frekvence delovanja procesorja se nahajajo v datoteki system_LPC13xx.c.

Za sistemsko uro je nastavljeno naslednje:
Koda:
#define SYSOSCCTRL_Val 0x00000001
#define SYSPLLCTRL_Val 0x00000025
#define SYSPLLCLKSEL_Val 0x00000001
#define MAINCLKSEL_Val 0x00000003
#define SYSAHBCLKDIV_Val 0x00000001
Čemu služijo navedeni ukazi?
Vse možnosti posameznih registrov so opisane na straneh 14-31 datoteke UM10375.pdf
SYSOSCCTRL 0b0000 0000 sistemski oscilator ni izpuščen
SYSPLLCTRL 0b0010 0101 MSEL = 5, P=2
SYSPLLCLKSEL 0b0000 0001 vir PLL je sistemski oscilator
MAINCLKSEL 0b0000 0011 vir glavne sistemske ure je PLL izhod
SYSAHBCLKDIV 0b0000 0001 glavna sistemska ura je deljena z 1 da dobimo sistemsko uro
Ukazi dobijo smisel, če pogledamo shemo zgradbe procesorja na na strani 14 datoteke UM10375.pdf. Nekaj več detajlov prvih dveh likov SYSPLLCLKSEL in SYS PLL sta prikazana na sliki na strani 49.

V našem primeru je pa ukaz SYSPLLCTRL 0b0010 0101 ( MSEL = 5, P=2) ki določa frekvenco in ga je potrebno malce podrobneje pogledati.
Vsebuje dva faktorja vrednosti za dva faktorja M in P, ki se uporabljata za ustrezno nastavitev PLL oscilatorja.
Faktor M deli izhodno frekvenco PLL za vrednost MSEL+1 v povratni zanki PLL oscilatorja
Faktor P pa deli frekvenco PLL z vrednostjo PSEL*2, da dobimo izhodno frekvenco FCLKOUT.

Faktorja morata biti takšna, da ustrezata enačbam:
M=FCLKOUT/FCLKIN FCLKIN = vhodna frekvenca PLL – v našem primeru 12MHz
FCCO=2xPxFCLKOUT FCCO = frekvenca PLL, ki mora biti v mejah 156 do 320MHz za stabilno delovanje PLL

V našem primeru so vrednosti MSEL=5 -> M=(MSEL+1)=6, ter PSEL=2 ->P=(PSEL*2)=4

Če malo premečemo enačbe dobimo:
FLCKOUT=12MHz*6 = 72MHz
FCCO=2*2*72MHz=288MHz , kar je <320 in >156MHz

Iz tega sledi, da naš CPU deluje na 72MHz.
Podobna kolobocija je potrebna za izračun frekvence USB pogona, pri čemer je potrebno nastaviti frekvenco 48MHz, ki jo zahteva USB standard. Zato sta faktorja M in P drugačna. (M=4 in P=2)

Del 7 Časovnik SYSTICK

Naš procesor ima vgrajen tudi poseben časovnik SYSTICK, ki je namenjen predvsem za uporabo RTOS, kot vir prekinitev, ki si sledijo v natančno določenem časovnem vrstnem redu. Kot standardna enota je predlagana 10ms, seveda pa lahko nastavimo relativno poljubno vrednost.
SYSTICK časovnik je 24 bitni in ob preteku vrednosti proži namensko prekinitev.
Tule ga bomo pa uporabili za izdelavo časovnika za zakasnitve v programski kodi.
(Uporabno za programski debounce, utripanje in en kup drugih funkcij)

Za uspešno uporabo SYSTICK časovnika je včasih potrebno nastaviti nekaj vrednosti registrov:
SYSTICKCLKDIV – register za vpis delilnika za uro na katero je priključen.
Če je vrednost 0 je časovnik izključen. Vpisana vrednost 255 deli vhodno uro z 255.
LPC_SYSCON->SYSTICKCLKDIV = 0x00000001 // delilnik z 1
(glej stran 28 datoteke UM10375.pdf)

Ker je SYSTICK mišljen kot zelo natančna ura, imamo na voljo register s katerim lahko natančnost ure popravimo z vpisom kalibracijske vrednosti v register SYST_CALIB
(glej stran 33 datoteke UM10375.pdf)

Kako pa zadeva deluje?
V register LOAD SYSTICK registra vpišemo vrednost. Časovnik ob vklopu procesorja šteje (glede na delilnik) do vrednosti vpisane v LOAD register, ko pride do vpisane vrednosti sproži prekinitev.

Kako pa pridemo do želene vrednosti?
Če hočemo zakasnitev 10ms.
V našem primeru deluje procesor na 72MHz
Torej pomnožimo 72000000 (72MHz) x 0,01 (10ms) = 720000.
Ker šteje od 0 in ne od 1 od dobljene vrednosti odštejemo še 1 torej 720000-1=719999, kar je enako 0x000AFC7F
Koda:
SysTick->LOAD = 0x000AFC7F; // Reload vrednost za 10ms pri 72MHz in delilniku 1.
Pred uporabo časovnika ga je potrebno ustrezno nastaviti z vpisom vrednosti v CTRL register.
Omogočiti moramo SYSTICK z vpisom 1 v bit 0, omogočiti moramo interupt z vpisom 1 v bit 1, ter izbrati vir ure za register SYSTICK z vpisom vrednosti v bit 3. Če v bit 3 višemo 0 bo časovnik vzel uro preko delilnika ure (LPC_SYSCON->SYSTICKCLKDIV), v nasprotnem primeru pa bo uporabljena kar ura jedra.
Koda:
SysTick->CTRL = 0x00000007; // Enable systick, Enable tickint, clksource=core
(glej strani 295-299 datoteke UM10375.pdf)

Kaj pa prekinitev?
Ime funkcije prekinitve je definira v datoteki Startup_LPC13xx.s. Kličemo jo pa takole:
Koda:
void SysTick_Handler(void) {
// tule naredimo nekaj;
}
Void kaj?
Void je definicija praznega tipa podatka. Torej naša funkcija prekinitve ne vrne, niti prejme nobene spremenljivke.

Kako potem zgleda programček?
Koda:

#include <stdio.h>
#include "LPC13xx.h" // LPC13xx definitions
#define LED2 (1<<8) // P0.8 LED2
#define LED3 (1<<9) // P0.9 LED3
#unsigned int systick_stevec; // števec tikov
void SysTick_Handler (void){ // prekinitvena rutina za SysTick register
systick_stevec++; // ko pride do prekinitve se poveča števec
}
void setup_SysTick(void){
// SysTick->LOAD = 0x000AFC7F; // Reload vrednost za 10ms pri 72MHz in delilniku 1.
SysTick->LOAD = 0x0001193F; // Reload vrednost za 1ms pri 72MHz in delilniku 1.
SysTick->CTRL = 0x00000007; // Omogoči systick, Omogoči tickint, clksource=core
}
void Delay_ms (unsigned int cas_ms) {
unsigned int stevilo_systikov;
stevilo_systikov = systick_stevec;
while (( systick_stevec - stevilo_systikov) < cas_ms); // V zanki dokler ne prešteje do cas_ms
}

int main (void) { // Main Program
setup_SysTick();
LPC_GPIO0->DIR |= LED2 | LED3; // |= (ALI) Vpišemo 1 v 8 bit registra DIR (LED=P0.8)
// Nastavitev smeri PortaP0(GPIO0) P0=izhod

while (1) { // Loop forever
LPC_GPIO0->DATA |= LED2; // |= (OR) Vpiše 1 v 8 bit registra DATA GPIO0.8 ugasne LED2
Delay_ms (10); // Pove ime funkcije
LPC_GPIO0->DATA &= ~LED2; // vpiše 0 v 8 bit registra DATA GPIO0.8 prizge LED2
Delay_ms (10);
}
}

Na začetku definiramo spremenljivko systick_stevec, ki je 32 bitna pozitivna številka.
Sledi definicija funkcije prekinitve void SysTick_Handler (void) Funkcija se izvede ko SYSTICK časovnik prišteje do vpisane vrednosti v LOAD register. Prekinitvene funkcije morajo biti kratke, da lahko predelajo vpisane naloge pred proženjem naslednje prekinitve.

Naslednja funkcija je namenjena konfiguraciji SYSTICK časovnika. Najprej sem imel v register LOAD vpisano vrednost za zamik 10ms, ki sem jo kasneje spremenil na 1ms po zgoraj opisanem postopku. Zakaj? Zaradi naslednje funkcije:

Delay_ms funkcija opravlja nalogo zakasnitve. Sprejme en parameter in to je cas_ms – to pa je število ms, ki jih hočemo za zakasnitev. Ker je en SYSTICK dolg 1ms, je štetje ms enostavno.
Številka, ki jo vpišemo med klicem funkcije je število ms.
Znotraj funkcije je definirana ena spremenljivka stevilo_systikov , ki je začasna spremenljivka za hranjenje vrednosti stanja spremenljivke systick_stevec Sledi zanka, ki na zelo inteligenten način reši problem brisanja spremenljivke systick_stevec. Ko pokličemo funkcijo Delay_ms praktično ne vemo stanje spremenljivke systick_stevec, saj se spremenljivka povečuje vsako ms. Zato stanje spremenljivke systick_stevec shranimo v spremenljivko stevilo_systikov .
Z zanko WHILE potem primerjamo razliko vrednosti spremenljivk z številko ms.
Zanka vztraja dokler je razlika manjša od številke ms. Kaj pa povečuje vrednosti? Spremenljivka systick_stevec se vsako ms poveča za 1. Razlika med obema spremenljivkama je tako vedno od 1 do želenih ms. Lahko začnemo z vrednostjo systick_steveca 400 ali 2003044.
Tako se z vsako prekinitvijo razlika povečuje. Ko je razlika dovolj velika se izvajanje funkcije prekine.

Arhiv Keil projekta:
Systick.zip

Del 8 Priklop 4x20 LCD displeja.

Velikokrat pri naših projektih prav pride uporaba LCD displeja. Med drugim ga lahko uporabimo tudi za razhroščevanje naših projektov, saj lahko izpisuje vrednosti spremenljivk, do katerih drugače nebi imeli dostopa. (Če seveda nimamo kakšnega uLink vmesnika ali kaj podobnega)

Najprej je bilo potrebno izbrati priključke na katere bom priključil LCD. Za to potrebujemo najmanj 7 priključkov. Štiri rabimo za prenos podatkov (D7, D6, D5, D4), 3 pa za krmiljenje LCD-ja (R/W, E, RS.

Po nekaj tuhtanja sem se odločil za Port GPIO2, ker se mi je zdel najbolj prazen. Torej malo pinov ima več funkcij. Žal so priklopi na te pine raztreščeni po štirih priključnih letvicah. V prilogi so označeni kateri pini so to.

Izdelal sem en 10 polni ploščati kabel, ki ima na eni strani prispajkano letvico z rastrom 2.54mm, na drugi strani pa ustrezno žensko letvico. Uporabno, če pine ločimo, tako da jih je možno poljubno priključiti.

LCD sem preko ene takšne letvice pritrdil na prototipno ploščico, ustrezne pine povezal na razvojno ploščico. Za napajanje LCD-ja je potrebno 5V, kar sem dobil z univerzalnega usmernika, ostali priključki pa so povezani direktno na našo razvojno ploščico. Seveda sem povezal tudi ničli obeh vezij. LCD potrebuje za svoje delo tudi priklop potenciometra na V0 priključek, da lahko nastavimo kontrast. Sam sem uporabil kar en 10K potenciometer vezan med 0 in +5V, srednji odcep pa je vezan na V0 LCD. Ko prižgemo LCD se mora na displeju (v mojem primeru 2) prikazati črna črta čez celo vrstico. Če ni vidna je potrebno nastaviti kontrast. To je običajno za LCD, ki še ni inicializiran. Ko se LCD-ju pošlje nekaj ukazov ta črta zgine.

Detajlov kako deluje LCD ne bom pisal, lahko pa si detaljni opis pogledate tule: Newbie hack. Čeprav je opisan postopek priklopa na AVR, so osnove enake za vse tipe procesorjev.

Na internetu se najde kar nekaj projektov, kjer je na voljo tudi C koda za poganjanje LCD displejev. Med njimi ni neke posebno velike razlike, razlikujejo se predvsem zato, ker so mišljene za uporabo na različnih platformah ali pa so mišljene za različne prevajalnike.

Sam sem zato začel pregledovati kodo, ki je na voljo kar poleg razvojnega okolja Keil. Žal za naš procesor ni bilo kode, ki bi jo bilo možno direktno uporabiti, sem pa našel kodo, ki je bila sicer namenjena za procesor LPC29xx

Da LCD uporabimo, je najprej v naš program vključiti lcd.h datoteko, nato pa že sledi inicializacija LCD displeja in uporaba po željah. Za uporabo displeja so spisane funkcije, ki omogočajo različne naloge:
Koda:
LCD_init (); // inicializacija LCD
LCD_gotoxy (); // skok kurzorja na lokacijo
LCD_cls (); // pobriše prikaz
LCD_cur_off (); // izklop kurzorja
LCD_putc (); // izpis znaka
LCD_print (); // izpis niza znakov
LCD_bargraph (); // risanje stolpca (horizontalno)
V opis delovanja posameznih funkcij se ne bom poglabljal bom pa opisal kaj sem spreminjal. Oziroma ob kaj sem se spotaknil.

Nastaviti je seveda potrebno na katere izhode procesorja bodo priključeni vhodi LCD displeja. Kako se to počne sem že napisal, zato samo koda:
Koda:
#define PIN_E (1 << 6) // ...0100 0000
#define PIN_RW (1 << 5) // ...0010 0000
#define PIN_RS (1 << 4) // ...0001 0000
#define PINS_CTRL (0x07 << 4) // ...0111 0000
#define PINS_DATA (0x0FUL << 0) // ...0000 1111
Zanimiva je tale postavka:
Koda:
(0x0FUL << 0)
Toje je številka Unsigned Long 0x0F

Tule je napisana koda, ki določa ali je na izhodu določenega pina 1 ali 0
Koda:
#define LCD_E(x) ((x) ? (LPC_GPIO2->DATA |= PIN_E) : (LPC_GPIO2->DATA &= ~PIN_E) ); delay(10);
Tole se bere takole:

LCD_E je v primeru, da je x = 1 (LPC_GPIO2->DATA |= PIN_E) torej gre pin na 1
x = 0 (LPC_GPIO2->DATA &= ~PIN_E) torej gre pin na 0
Na koncu je ne glede na to kaj smo izbrali še dodan delay(10)
LCD_E(1) ali LCD_E(0) kličemo znotraj našega programa. Prevajalnik potem ta LCD_E(1) zamenja s kodo:
LPC_GPIO2->DATA |= PIN_E; delay(10);
LCD ima vgrajeno tabelo standardnih znakov. Nekateri bolj nekateri manj. (ASCII) Običajno pa imajo nekaj prostora tudi za namenske znake, ki jih lahko izdelamo sami in ob zagonu pošljemo na LCD. Ta jih hrani v svojem ramu. Če jih potrebujemo jih enostavno pokličemo. Temu služi tale kos kode, ki vsebuje informacijo o obliki znakov.
Koda:
const unsigned char UserFont[8][8] = {
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // prazen
{ 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10 }, // |
{ 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18 }, // ||
{ 0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C }, // |||
{ 0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E,0x1E }, // ||||
{ 0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F }, // |||||
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // prazen
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 } // prazen
};
Nekatera razvojna orodja imajo že vgrajene pripomočke, kjer enostavno narišemo znak, pripomoček pa zgenerira zapis tega znaka. Sam uporabljam tudi razvojno orodje Mikroelektronike (sicer za PicBasic+), ki ima takšno orodje že na voljo. Zapis je zelo enostavno prevesti v obliko, ki je prikazana zgoraj. Važni so le podatki, ki pa so identični.

Težave sem imel s funkcijo, ki preračunava pozicijo na katero skušamo prikazati trenutni znak.
Koda:

void LCD_gotoxy (unsigned char x, unsigned char y) {
unsigned char address;
// address = (y * 40) + x; // originalna koda
// address = 0x80 + (address & 0x7F); // originalna koda
address = (0x80 + LCD_frst_locations[y-1] + (x-1)); // lokacija pisanja
lcd_write_cmd(address); // Set DDRAM address counter to 0
}
Verjetno je bil displej uporabljen za primer drugačenm saj so se znaki izpisovali narobe. Zakaj sem ugotovil potem, ko sem šel preračunat vrednost spremenljivke address za nekaj različnih lokacij.

Kljub temu, da mi vidimo LCD kot 4 vrstice po 20 znakov bi bile te lahko nanizane ena za drugo od 1 do 80 Vendar temu ni tako. Moj LCD ima prve znake v posameznih vrsticah na teh lokacijah: 0, 64, 20, 84 - potrebno preverit za displej, ki ga mislimo uporabiti. Zato sem dodal še eno spremenljivko LCD_first_locations ki je deklarirana takole:
Koda:
char LCD_frst_locations[4] = {0, 64, 20, 84};
Vsebuje naslove skrajno levih polj znakov posameznih vrstic.
Tako se vsebina vpisuje v pravo vrstico.
Ko kličemo funkcijo LCD_print (3, 2, "Besedilo"); je 3 vrstica, 2 kolona
Če to vstavimo v enačbo:
Koda:
address = (0x80 + LCD_frst_locations[3-1] + (2-1))
address = (0x80 + LCD_frst_locations[2] + 1)
ker je LCD_frst_locations[2] = 20 je končna enačba
Koda:
address = (0x80 + 20 + 1) = 21
kar je 2 mesto z leve v tretji vrstici displeja
(0x80 odmislimo - je potrebno za pravilno delovanje LCD 0b10000000=0x80= ukaz za pomik na prvo mesto na displeju 0,0)

Če ima naš displej manj vrstic, je potrebno število vrstic v LCD_frst_locations[4] = {0, 64, 20, 84} zmanjšati na 2 in ustrezno popraviti začetna polja.

Še nekaj. Pri uporabi LCD displejev je vedno imeti v mislih, da so displeji počasni. Potrebujejo nekaj časa, da izvedejo ukaze zato ni dovolj, da ukaz samo pošljemo displeju potrebno je nekaj časa počakati. Detajlni opisi so v priloženi datoteki za čip HD44780, ki je vgrajen (ali njegovi kloni) v večino majhnih displejev. Zato je v kodi en kup zakasnitev. Sicer so izvedene s štetjem, ko bo čas pa bom uporabil vgrajene časovnike.

Programček zapisuje vrednosti od 1 do 20 v posamezne celice v vrstice po vrsti. Začne levo zgoraj konča desno spodaj. Ker za ničlo pri 20 ni prostora se le ta izpisuje drugje.

Še datoteke osmega dela:
Shema priklopa LCD: vezava_lcd.pdf
HD44780.pdf
Keil projekt: LCD.zip

Del 9: DS1820

Ob pomoči nekaterih članov foruma, ki jih ne bom imenoval (Umnik, Igo Wink ) sem usposobil nekaj preprostih funkcij, ki omogočajo uporabo DS1820 senzorja.
Za različico DS18S20 je potrebno kodo spremeniti.

Zaenkrat še ni tako pametna, da bi se sama prilagajala tipom senzorjev.
Primerna je za uporabo enega senzorja.
V prvo vrstico se izpiše naslov senzorja, ki je priključen.
Izpisano kodo je potrebno prepisati v DS1820.c, (obstoječo pa odstraniti)
Sicer se da vrednost scratchpada enega senzorja prebrati tudi brez naslova senzorja. To je prikazano v drugi vrstici, kjer se naslov senzorja ne uporabi.

Popravljal sem tudi kodo za systick, saj se je izkazalo, da ima hrošča. Ob dolgem delovanju se je - vsaj na mojem LCD-ju prikaz sesul. Očitno se je števec takrat obrnil. To pa je povzročilo napako v dolžini signalov, ki je po določenem času sesula prikaz na prikazovalnika

Še datoteke tega dela:
Keil projekt: LCD_DS1820.zip

Del 10: Priklop in uporaba senzorja SHT75

Tole je pa še v delu


P&P 30.12.2012