IQMath
Texas Instruments TMS320C28x IQmath biblioteka je kolekcija visoko optimizovanih i visoko preciznih matematičkih funkcija za C\C++ programere koja im omogućava da, na TMS320C28x uređajima, jednostavno portuju algoritam pisan u pokretnom zarezu u algoritam pisan u nepokretnom zarezu. Ove rutine se obično koriste za računski intenzivne real-time aplikacije gde su optimalna brzina izvršenja i visoka tačnost kritični. Korišćenjem ovih rutina može se postići znatno veće brzine izvršavanja programa nego brzine ekvivalentnog programa napisanog u standardnom ANSI C jeziku. Pored toga, postojanje biblioteke sa funkcijama visoke preciznosti, spremne za upotrebu, kakva je TI IQmath biblioteka, može značajno skratiti vreme razvoja DSP aplikacija.
Uputstvo za rad sa IQMath bibliotekom se nalazi na linku.
IQMath biblioteka je sastavni deo controlSUITE-a ali se distribuira i kao samoraspakujuća ZIP arhiva (paket) i može se downloadovati sa TI sajta. Ako ju je instalirao controlSuite, biblioteka se najčešće nalazi u direktorijumu C:\ti\controlSUITE\libs\math\IQmath.
Instalacija i primer korišćenja biblioteke
Pogledajmo sledeći kod kojim možemo porediti brzinu rada i preciznost _IQsin()
i sin()
funkcija:
#include <IQmathLib.h>
#include <math.h>
#define PI 3.14159
void main() {
float ugao = 0.25*PI;
float vrednost;
_iq qugao;
_iq qvrednost;
vrednost = sin(ugao); // racuna sin ugla; t=1926
qugao=_IQ(ugao); // konvertuje float u Q; t=245
qvrednost =_IQsin(qugao); // racuna sin ugla; t=51
vrednost = _IQtoF(qvrednost); // konvertuje Q u float; t=24
while(1);
}
U kodu se pojavljuje nekoliko novih stvari:
#include <IQmathLib.h>
- u kod uključujemo IQmath biblioteku._iq
- novi tip promenljive. Ovo je zapravo način prikazivanja razlomljenih brojeva u nepokretnom zarezu, takozvani Q format. Primer i opis Q15 formata dat je u jednom od ranijih poglavlja u priručniku. Tip može biti opšti(_iq
) kako je prikazano u primeru ili se može specificirati, npr._iq15
što odgovara Q15 formatu zapisa. Opšti tip je specificiran aliasomGLOBAL_Q
koji se nalazi uIQmathLib.h
fajlu kao 24, što znači da je tip_iq
isto što i_iq24
. Promenom vrednosti ovog aliasa biće promenjen i opšti Q tip._IQ()
- funkcija koja konvertuje broj iz float tipa u IQ vrednost._IQsin()
- funkcija vraća vrednost sinusa ugla._IQtoF()
- funkcija koja konvertuje broj iz Q u float tip.
Ako kreiramo novi projekat i pokušamo da pokrenemo ovaj program, kompajler će vratiti grešku. U mom slučaju, pri prvom bildovanju projekta, kompajler je vratio grešku: #1965 cannot open source file "IQmathLib.h". Ova greška znači da kompajler ne može da nađe biblioteku. Zato je potrebno da mu ručno ukažemo gde se biblioteka nalazi i dodamo sve neophodne fajlove. Najjednostavnije je to uraditi tako što ćemo u podešavanjima projekta postaviti put prema biblioteci. Izborom opcije Properties u meniju Project, na ekranu će se pojaviti podešavanja za projekat.
Mi trebamo da u Build -> C2000 Compiler -> Include options, dodamo putanju u kojoj se nalazi biblioteka, tako što ćemo kliknuti na ikonicu listića sa zelenim plusićem u polju "Add dir to #include search path". Pojaviće se prozor za unos puta, gde je potrebno izbrati ili ukucati put: "C:\ti\controlSUITE\libs\math\IQmath\v160\include". To je direktorijum u kome se obično nalaze include fajlovi za IQmath biblioteku, ako ju je instalitao controlSUITE. Klikom na dugme OK, put do biblioteke će biti dodat u takozvani search path, odnosno, kompajleru će u buduće biti jasno gde da traži biblioteku.
Ako ponovo pokušamo da bildujemo projekat, videćemo da je prethodna greška rešena, međutim, pojaviće se nove greške, u mom slučaju: unresolved symbol _IQ24sin, first referenced in .\/main.obj. Ova greška znači da kompajler ne može da nađe biblioteku sa simbolima. Najjednostavniji način je dodati biblioteku u projekat tako što ćemo desnim tasterom miša kliknuti na projekat i izabrati opciju Add Files ...
Na ekranu će se pojaviti prozor za izbor fajlova, a mi treba da izaberemo fajl "IQmath.lib" koji se nalazi u direktorijumu "C:\ti\controlSUITE\libs\math\IQmath\v160\lib".
Na sledećem prozoru je potrebno ostaviti izabranu opciju "Link to files" i štiklirano "Create link locations relative to: PROJECT_LOC" i kliknuti na dugme OK.
Podešavanja u ovom prozoru nisu preterano bitna ali smo ovime "rekli" razvojnom sistemu da napravi simbolički link prema biblioteci i da put prema biblioteci ne bude apsolutna putanja već relativna u odnosu na direktorijum u kome se projekat nalazi. Videćemo da je projektu dodat fajl "IQmath.lib".
Na isti način je potrebno dodati biblioteku "2802x_IQmath_BootROMSymbols.lib" iz direktorijuma "C:\ti\controlSUITE\libs\utilities\boot_rom\2802x\2802x_BootROMSymbols_v2_0".
Ako je sve urađeno kako treba, projekat bi trebalo, ovog puta, da se bilduje bez greške.
Kada izmerimo vreme rada funkcija sin()
i _IQsin()
, videćemo da _IQsin()
radi 51 takt kloka, a sin()
1926 taktova kloka. To znači da se _IQsin()
izvršava skoro 40 puta brže od sin()
, odnosno se na DSP-u od 60MHz, ona izvršava 0.85us. To je, za razliku od sin()
, dovoljno brzo za obrade nekih signala u realnom vremenu.
Kada uporedimo preciznosti rezultata ove dve funkcije sa vrednošću koju vraća online servis Wolfram Alpha, videćemo da se razlika javlja na 7 decimali, što je u velikom broju aplikacija prihvatljiva tačnost.
Blinking LED
Najjednostavniji način da se proba rad razvojnog sistema je da se na razvojnom sistemu napiše program koji će paliti i gasiti LED. U ovom poglavlju ćemo se baviti upravo time; na razne načine paliti i gasiti LED (LD2).
Razvojni sistem koji ćemo koristiti u ovom poglavlju je PICCOLO controlSTICK 28027. Kao što samo ime sugeriše, srce ovog razvojnog sistema je Texas Instruments-ov TMS320F28027 mikrokontroler. Razvojni sistem ima XDS100 emulator. XDS100 je Texas Instruments-ov jeftin JTAG interfejs koji se sa računarom povezuje preko USB-a. Zahvaljujući njemu, za programiranje razvojnog sistema nije potreban nikakav dodatni hardver. Dovoljno je razvojni sistem ubaciti u slobodan USB port, a razvojno okruženje (CCS) će odraditi ostalo.
Na razvojnom sistemu se nalazi header na kome su izvučeni razni pinovi mikrokontrolera. Header služi za jednostavno povezivanje razvojnog sistema i proto ploče, odnosno jednostavno povezivanje razvojnog sistema i našeg hardvera. Raspored pinova na headeru je prikazan u sledećoj tabeli:
Iz tabele se može videti da razvojni sistem ima programabilne priključke koji mogu da se koriste kao:
- Analogno digitalni konverteri (ADC),
- Komparatori (COMP),
- Širinski modulisani (PWM) izlazi,
- I2C interfejs,
- SPI interfejs i kao
- Digitalni ulazno\/izlazni pinovi (GPIO)
Naša LED je povezana na pinu 30 odnosno GPIO34 priključku mikrokontrolera. Šema povezivanja LED je prikazana na slici.
Sa slike se može videti da setovanjem pina 34 gasimo LED, a resetovanjem palimo. Naime, kada setujemo GPIO34, mi ga zapravo postavljamo na logičku jedinicu, što je u našem slučaju 3.3V, a što je i napon napajanja LED. Između ove dve tačke ne može da poteče struja jer se nalaze na istom potencijalu (3.3V). Ako resetujemo GPIO34, odnosno postavimo ga na logičku nulu, što je u našem slučaju 0V ili masa uređaja, pravimo razliku potencijala i struja može da poteče. Otpornici R1 i R2 ograničavaju struju koja će teći kroz GPIO34 odnosno LED. Gore prikazana šema dela GPIO-34 koji se nalazi u CPU je slikovito prikazana i u stvarnosti nije ovakav prekidač.
Zadaci
Zadatak 1
Napraviti program u CCS-u koji će paliti i gasiti LED u:
- jednakim vremenskim intervalima,
- jednakim vremenskim intervalima u trajanju od 1s,
- u različitim vremenskim intervalima, tako da se ukupno vreme trajanja intervala i vreme svetlenja može podešavati.
- Menjati osvetljaj LED.
Priprema razvojnog okruženja
ControlSUITE sadrži demo projekte za razne razvojne sisteme. Između ostalog sadrži i projekat koji nama treba. Da bi već postojeći projekat ubacili u razvojno okruženje potrebno je odabrati opciju "Import Existing CCS Eclipse Project" iz menija "Project".
Na ekranu će se pojaviti dialog za izbor projekta. Naš projekat se nalazi na lokaciji "C:\ti\controlSUITE\development_kits\Piccolo controlSTICK\Timer - BlinkingLED". Potrebno je klikom na dugme Browse ... odabrati tu lokaciju i u polju Discovered projects izabrati "Blinking LED" projekat. Pre klika na Finish dugme, potrebno je odabrati i opciju "Copy projects into workspace".
Na ekranu će se pojaviti pregled datoteke "BlinkingLED-Main.c". Ukoliko datoteka nije ista kao na sledećem listingu, prekopirati je.
//----------------------------------------------------------------------------------
// FILE: BlinkingLED-Main.C
//
// Description: This program blinks LD2 on the Piccolo controlSTICK at a
// frequency given by the CPU timer period register. Change the
// register/bits CpuTimer0Regs.PRD.all to change the frequency of
// the LED (LD2).
//
// Target: TMS320F2802x or TMS320F2803x families (Piccolo)
//
//----------------------------------------------------------------------------------
// $TI Release:$ V1.1
// $Release Date:$ 26 Oct 2009 - BRL
//----------------------------------------------------------------------------------
//
// PLEASE READ - Useful notes about this Project
// Although this project is made up of several files, the most important ones are:
// "BlinkingLED-Main.c" - this file
// - Application Initialization, Peripheral config
// - Application management
// - Slower background code loops and Task scheduling
// "BlinkingLED-DevInit_F28xxx.c"
// - Device Initialization, e.g. Clock, PLL, WD, GPIO mapping
// - Peripheral clock enables
// The other files are generally used for support and defining the registers as C
// structs. In general these files will not need to be changed.
// "F28027_RAM_BlinkingLED.CMD" or "F28027_FLASH_BlinkingLED.CMD"
// - Allocates the program and data spaces into the device's memory map.
// "DSP2802x_Headers_nonBIOS.cmd" and "DSP2802x_GlobalVariableDefs.c"
// - Allocate the register structs into data memory. These register structs are
// defined in the peripheral header includes (DSP2802x_Adc.h, ...)
//
//----------------------------------------------------------------------------------
#include "PeripheralHeaderIncludes.h"
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// FUNCTION PROTOTYPES
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void DeviceInit(void);
void InitFlash(void);
void MemCopy(Uint16 *SourceAddr, Uint16* SourceEndAddr, Uint16* DestAddr);
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// VARIABLE DECLARATIONS - GENERAL
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Used for running BackGround in flash and the ISR in RAM
extern Uint16 RamfuncsLoadStart, RamfuncsLoadEnd, RamfuncsRunStart;
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// MAIN CODE - starts here
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void main(void)
{
//=================================
// INITIALISATION - General
//=================================
DeviceInit(); // Device Life support & GPIO mux settings
// Only used if running from FLASH
// Note that the variable FLASH is defined by the compiler (-d FLASH)
#ifdef FLASH
// Copy time critical code and Flash setup code to RAM
// The RamfuncsLoadStart, RamfuncsLoadEnd, and RamfuncsRunStart
// symbols are created by the linker. Refer to the linker files.
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
// Call Flash Initialization to setup flash waitstates
// This function must reside in RAM
InitFlash(); // Call the flash wrapper init function
#endif //(FLASH)
// Initialise Period of Cpu Timers
// Timer period definitions found in PeripheralHeaderIncludes.h
CpuTimer0Regs.PRD.all = mSec500; // 500ms * 2(# of LED states) = 1s blink rate
// CpuTimer1Regs.PRD.all = mSec1000;
// CpuTimer2Regs.PRD.all = mSec5000;
//=================================
// INTERRUPT INITIALISATION (not needed in this example)
// (best to run this section after other initialisation)
//=================================
// Enable Peripheral, global Ints and higher priority real-time debug events:
// IER |= M_INT3;
// EINT; // Enable Global interrupt INTM
// ERTM; // Enable Global realtime interrupt DBGM
//=================================
// Forever LOOP
//=================================
for(;;) //infinite loop
{
if(CpuTimer0Regs.TCR.bit.TIF == 1)
{
CpuTimer0Regs.TCR.bit.TIF = 1; // clear flag
//-----------------------------------------------------------
GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1; //Toggle GPIO34 (LD2)
//-----------------------------------------------------------
}
}
} //END MAIN CODE
Potom je potrebno definisati Target Configuration, na način objašnjen u ranijim poglavljima. Da bi sve radilo kako treba Target Configuration treba da bude kao sa slike i podešen da je Active.
Klikom na Test Connection, proverićemo da li razvojno okruženje "vidi" hardver. Poruka "The JTAG DR Integrity scan-test has succeeded." znači da je sve u redu.
Da bi razvojno okruženje bilo u potpunosti spremno za rad, potrebno je desnim tasterom miša kliknuti na projekat i iz padajućeg menija izabrati Build Configuration -> Set Active -> F2802x_RAM.
Priključiti PICCOLO controlSTICK u USB port računara i buildovati projekat. LED bi trebala da se pali i gasi. Razvojno okruženje je spremno za dalji rad.
Rešenje
Kako bi bilo lakse za razumevanje, zamenićemo kod iz primera sa sledećim kodom:
#include <IQmathLib.h>
#include "PeripheralHeaderIncludes.h"
// definicija konstanti
#define _PI 3.14159
#define _2PI 6.28318
// deklaracija prototipova funkcija koje se nalaze u fajlu:
// BlinkingLED-DevInit_F2802x.c
void DeviceInit(void);
void InitFlash(void);
void MemCopy(Uint16 *SourceAddr, Uint16* SourceEndAddr, Uint16* DestAddr);
extern Uint16 RamfuncsLoadStart, RamfuncsLoadEnd, RamfuncsRunStart;
// Funkcije koje ce biti koriscene u ovom primeru
void paliLED() {
GpioDataRegs.GPBCLEAR.bit.GPIO34 = 1; // Pali LED
}
void gasiLED() {
GpioDataRegs.GPBSET.bit.GPIO34 = 1; // Gasi LED
}
void cekaj(long i) { // moguca, ali ne tako dobra realizacija cekanja
long cekalica;
cekalica=i;
while(cekalica--);
}
/*
* Glavni program
* Zadatak 1.1
*/
void main(void) {
DeviceInit(); // Inicijalizacija hardvera
#ifdef FLASH
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitFlash(); // Call the flash wrapper init function
#endif //(FLASH)
while(1) {
paliLED();
cekaj(10000000); // t=70,000,032
gasiLED();
cekaj(10000000);
}
}
To je ujedno i rešenje zadatka 1.1.
Funkcija cekaj()
, nije najsrećnije rešenje jer vreme koje čeka ne odgovara parametru funkcije. Tako na primer, ako stavimo kao parametar funkciji 10 miliona, ona će čekati nešto više od 1s odnosno oko 70 miliona ciklusa, što svakako nije zgodna relacija.
Da bi ovo popravili, funkciju cekaj()
ćemo zameniti funkcijom pcekaj()
.
void podesiTajmer(long i) {
if (i>0) {
CpuTimer0Regs.PRD.all = i; // Setujem tajmer na i broj otkucaja kloka
CpuTimer0Regs.TCR.bit.TRB = 1; // Resetujem Timer (Timer Reload Bit)
CpuTimer0Regs.TCR.bit.TIF = 1; // Resetujem TIF
}
}
void cekajDaIstekneTajmer() {
// Kada istekne vreme postavljeno u PRD (Period) registar dogodice se prekid tajmera
// Da se prekid desio mozemo videti posmatrajuci TIF (Timer Interrupt Flag),
// Koji ce se u trenutku prekida postaviti na 1.
while(!CpuTimer0Regs.TCR.bit.TIF); // Cekam da istekne tajmer, odnosno da se desi interrupt
CpuTimer0Regs.TCR.bit.TIF = 1; // Resetujem TIF
}
void pcekaj(long i) {
podesiTajmer(i);
cekajDaIstekneTajmer();
}
Funkcija pcekaj()
meri vreme koristeći hardverski tajmer DSP-a. Ona poziva dve funkcije:
podesiTajmer()
- postavlja vrednost tajmera u odgovarajući registar i setuje odgovarajuće bitove kako bi startovala tajmer.cekajDaIstekneTajmer()
- čeka da se desi prekid tajmera odnosno da tajmer istekne.
Ovakva realizacija čekanja i dalje nije super precizna, ali je mnogo preciznija od prethodne verzije. Naime, samo pozivanje funkcija traje neko vreme tako da će funkcija čekati koji takt kloka više nego što tražimo od nje. Međutim, za svrhe učenja i demostracije rada, njena preciznost je zadovoljavajuća.
Ako main()
funkciju zamenimo sledećim kodom, dobićemo rešenje zadatka 1.2, naime, LED će se naizmenično paliti i gasiti tako da svetli jednu sekundu i da je ugašena jednu sekundu. Ukupno trajanje periode ovakvog dogadjaja je 2s.
/*
* Glavni program
* Zadatak 1.2
*/
void main(void) {
DeviceInit(); // Inicijalizacija hardvera
#ifdef FLASH
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitFlash(); // Call the flash wrapper init function
#endif //(FLASH)
while(1) {
paliLED();
pcekaj(60000000); // t=60,000,054
gasiLED();
pcekaj(60000000);
}
}
Kao što se vidi iz koda, parametar funkciji pcekaj()
je 60 miliona. To je zato što naš DSP radi na kloku od 60MHz odnosno 60 miliona ciklusa u sekundi. Drugim rečima, mi čekamo da prođe 60 miliona taktova kloka odnosno da prođe 1s. Da smo kao parametar stavili 30 miliona, funkcija pcekaj()
bi cekala 0.5 sekunde.
Kako bi rešili zadatak 1.3, uvešćemo novu funkciju softPWM()
.
void softPWM(long tDuty, long tSirina) {
paliLED(); pcekaj(tDuty);
gasiLED(); pcekaj(tSirina-tDuty);
}
Funkcija zapravo radi ono što smo radili u while()
pelji, ali su joj parametri širina intervala i vreme svetlenje LED. Glavni program koji koristi ovu našu funkciju biće:
/*
* Glavni program
* Zadatak 1.3
*/
void main(void) {
DeviceInit(); // Inicijalizacija hardvera
#ifdef FLASH
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitFlash(); // Call the flash wrapper init function
#endif //(FLASH)
long interval = 60000000; // 1s
long duty = 0.3*interval;
while(1) {
softPWM(duty,interval);
}
}
Ovaj program pali i gasi LED tako da je odnos vremena dok je ona ugašena i upaljena 30:70.
Ako interval, koji nam je u prethodnom primeru 1s, smanjimo na takvu vrednost da ljudsko oko ne vidi promene, promenićemo osvetljaj LED. Voditi računa o inverznoj logici LED diode!
long interval = 60000; // 1ms
long duty = 0.1*interval;
Promenom intervala na 60 hiljada, odnosno periode menjanja stanja LED na 1ms, učinili smo da se paljenje i gašenje menja 1000 puta u sekundi. To je više nego dovoljno brzo da ljudsko oko ne vidi promenu i da mu se čini da LED menja osvetljaj. Pošto ljudsko oko može da percipira maksimalno 25 slika u sekundi, ono se ponaša kao filter propusnih niskih frekvencija sporijih od 25Hz, odnosno low pass filter.
Zadatak 2
Na izlaz GPIO34, koristeci softverski PWM napravljen u prvom zadatku i IQmath biblioteku, poslati signal sinusoie potrebnog broja odbiraka, tako da frekvencija bude 0.5Hz, za sirinu impulsa od 60000 odbiraka.
Rešenje
/*
* Glavni program
* Zadatak 2
*/
void main(void) {
DeviceInit(); // Inicijalizacija hardvera
#ifdef FLASH
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitFlash(); // Call the flash wrapper init function
#endif //(FLASH)
long i;
long duty;
long sirina = 60000;
long brOdbiraka = 2000;
_iq korak = _IQ(_2PI)/brOdbiraka;
while(1) {
for (i=0; i<brOdbiraka; i++) {
duty=sirina*_IQtoF(_IQsin(i*korak)+_IQ(1))/2; // racunam sinus i dodajem mu DC ofset da bude unipolaran
softPWM(duty, sirina);
}
}
}
Ako je sve urađeno kako treba, dioda će se paliti i gasiti postepeno. Period paljenja i gašenja će biti 2s, odnosno frekvencije 0.5Hz.
Zadatak 3*
Napraviti program u CCS-u koji će upaljenu LED postepeno gasiti, drugim rečima, na izlaz GPIO34, koristeci softverski PWM napravljen u prvom zadatku i IQmath biblioteku, poslati signal opadajuće rampe, potrebnog broja odbiraka. Gašenje LED treba da traje 2s, a širina impulsa 30000 odbiraka. Ovaj ciklus ponavljati u beskonačnoj petlji.