IAR
KOMPILATOR V4
Kompilator V3 umożliwia pisanie programów w języku C lub C++. Dla obydwu
języków można wybrać wersję podstawową (standard języka) lub rozszerzoną.
Ustawień dokonuje się w nakładce IDE.
Dostęp do nazw rejestrów specjalnych i wybranych bitów poprzez dołączenie do
programu źródłowego pliku iodevice.h, gdzie device typ procesora, np.:
io430x14x.h.
Dla asemblera odpowiedni plik ma nazwę mspdevice.h, np.:
msp430x14x.h.
Zmienna typu bool jest dostępna w kompilatorze C++. Jeżeli ma być użyta w C, to należy do
programu dołączyć plik z biblioteką stdbool.h.
Zmienne całkowite:
zmienna
rozmiar
zakres
rozmieszczenie
bool*
1 bit
0 - 1
1
char
8 bitów
0 – 255
1
signed char
8 bitów
- 128 - +127
1
unsigned char
8 bitów
0 - 255
1
signed short
16 bitów -32768 - 32767
2
unsigned short
16 bitów 0 – 65535
2
signed int 16
16 bitów -32768 - 32767
2
unsigned int
16 bitów 0 – 65535
2
signed long 32
32 bity
2
31
– 2
31
- 1
2
unsigned long
32 bity
0 - 2
32
- 1
2
signed long long 64 bity
-2
63
– 2
63
- 1
4
unsigned long
64 bity
0 - 2
64
- 1
4
Rozmieszczenie danych w
pamięci:
Zmienna dwubajtowa: 0x1234
1 2 3 4
adres
ofset
1
0
Zmienna czterobajtowa:
0x12345678
1 2 3 4
adres
ofset
1
0
5 6 7 8
3
2
Liczby zmiennoprzecinkowe - wg standardu IEEE 754
Eksponenta Mantysa
S
31 30 23 22
0
Typ zmiennej
Zakres
double = 32
Zakres
double = 64
float
32 bity
32 bity
double
32 bity
64 bity
long double
32 bity
64 bity
Zmienna 32 bitowa:
zakres ±1.18E-38 to ±3.39E+38
wyrażenie (-1)
S
* 2
(Exponent-127)
* 1.Mantissa
położenie
precyzja operacje (+, -, *, /) w przybliżeniu 7 cyfr dziesiętnych
Eksponenta Mantysa
S
63 62 52 51
0
Zmienna 64 bitowa:
eksponenta 11 bitów
mantysa 51 bitów
zakres ±2.23E-308 to ±1.79E+308
wyrażenie (-1)
S
* 2
(Exponent-1023)
* 1.Mantissa
położenie
precyzja operacje (+, -, *, /) w przybliżeniu 15 cyfr dziesiętnych
Zero jest reprezentowane przez w postaci zera mantysy i eksponenty. Bit znaku
określa zero dodatnie lub ujemne.
Nieskończoność jest przedstawiania przez ustawienie maksymalnej wartości
eksponenty i zera mantysy.
Brak liczby (NaN) jest przedstawiane przez ustawienie maksymalnej dodatniej
wartości eksponenty i jedynki na najbardziej znaczącej pozycji mantysy. Wartośc
znaku jest ignorowana.
UWAGA: Biblioteka CLIB nie w pełni obsługuje specjalne przypadki liczb zmienno
przecinkowych takie jak nieskończoność i NaN. Należy dokładnie zapoznać się z
funkcjami obsługującymi te szczególne przypadki.
Wskaźniki adresowe - dwa rodzaje:
- wskaźniki do programu
- wskaźniki do danych
Dla procesorów MSP430 oba typy wskaźników są 16 bitowe
Dla procesorów MSP430X wskaźnik do danych jest 16 bitowy i umożliwia
adresowanie 64 KB pamięci, a wskaźnik do programu jest 32 bitowy i umożliwia
adresowanie 1 MB pamięci.
Wstawki asemblerowe:
W tekście źródłowym pisanym w C/C++ można robić wstawki asemblerowe po
poleceniu __asm lub asm. Polecenie __asm działa zawsze, natomiast asm nie działa w
C z opcją --strict_ansi.
Użycie jest następujące: asm(”łańcuch_znakowy”)
gdzie łańcuch_znakowy oznacza instrukcję asemblerową lub asemblerową definicję
danej, ale nie może być komentarzem. Np.:
......
__asm("MOV.B &P1IN,&flag");
asm(”Label: nop\n”
”jmp Label”);
Ograniczenia:
optymalizacja wykonywana przez kompilator może być znacznie ograniczona dla
instrukcji
asemblerowych,
dyrektywy asemblera mogą wywoływać błędy lub być pomijane,
nie jest kontrolowane poprawne położenie zmiennych,
nie ma dostępu do zmiennych automatycznych.
Funkcje.
Funkcja może być z atrybutem lub nie. Miejsce umieszczenia atrybutu podaje
poniższy przykład:
__interrupt void int_func(void)
{.....}
lub
void(__interrupt int_func)(void)
{.....}
lub
#pragma type_attribute=__interrupt
void int_func(void)
{.....}
Funkcje obsługujące przerwania:
- Przerwania podstawowe
#pragma vector = 0x14
--interrupt void int_func(void)
{.......}
-Generator przerwań (Timer A, Timer B, I
2
C, ADC12)
#pragma vector=TIMERA1_VECTOR
__interrupt void Timer_A1_ISR(void)
{
switch (__even_in_range(TAIV, 10))
{
case 2: P1POUT = 0x04;
break;
case 4: P1POUT = 0x02;
break;
case 10: P1POUT = 0x01;
break;
}
}
Funkcja obsługująca przerwanie chowa na stos wszystkie używane rejestry. Można to
zmienić przez użycie polecenia __raw lub __task:
__raw __interrupt void int_func()
__task __interrupt void int_func()
Polecenie __monitor blokuje przerwanie dla funkcji, przed którą występuje:
__monitor int test(void);
Funkcje ustawiające stan przerwań:
void __enable_interrupt(void);
//odblokowanie przerwania
void __disable_interrupt(void);
//zablokowanie przerwania
Łączenie programów pisanych w C i w asemblerze.
Parametry mogą być wnoszone do funkcji poprzez rejestry lub/i stos. Do
wnoszenia parametrów są wykorzystywane następujące rejestry:
Parametry
Rejestry
8 bitowe
R12, R14
16 bitowe
R12, R14
32 bitowe
R13:R12, R15:R14
64 bitowe
R15:R14:R13:R12,
R11:R10:R9:R8
Pozostałe parametry wejściowe są wnoszone poprzez stos:
Zmienne lokalne
Parametr n
........
Parametr 2
Parametr 1
Adres powrotu
Wolna pamięć stosu
wskaźnik stosu
adres wysoki
adres niski
Poprzez stos są również wnoszone parametry związane z uniami, strukturami i
klasami, a także nienazwane parametry do funkcji o zmiennej liczbie parametrów,
np. funkcji printf.
Zwracanie parametrów przez funkcje:
Parametr
Rejestr
8 bitowy
R12
16 bitowy
R12
32 bitowy
R13:R12
64 bitowy
R15:R14:R13:R12
Jeżeli funkcja zwraca strukturę, adres w pamięci, pod którym struktura się
znajduje jest przekazywany poprzez rejestr R12 jako parametr ukryty.
Przykład 1:
int prz_1(int a);
Asembler:
add
#1,R12
ret
Przykałd 2:
struct a_struct { int a; };
int a_function(struct a_struct x, int y);
Funkcja wywołująca rezerwuje na górze stosu 4 bajty gdzie umieszcza dane
związane ze strukturą. Parametr y jest przenoszony poprzez rejestr R12. Wartośc
funkcji jest zwracana również w rejestrze R12.
Przykład 3:
struct a_struct { int a; };
struct a_struct a_function(int x);
Wskaźnik do struktury, która jest umieszczona na stosie, jest przenoszony jako
parametr ukryty w rejestrze R12. Zmienna x jest przenoszona poprzez rejestr R14.
Wskaźnik do struktury jest zwracany poprzez rejestr R12.
Funkcja szkieletowa:
extern int gInt;
extern double gDouble;
int func(int arg1, double arg2)
{
int locInt = arg1;
gInt = arg1;
gDouble = arg2;
return locInt;
}
int main()
{
int locInt = gInt;
gInt = func(locInt, gDouble);
return 0;
}
Optym,alizacja musi być ustawiona na niskim poziomie.
Funkcje specjalne
a = __bcd_add_short(b, c)
- operacje dodawania na liczbach BCD
a = __bcd_add_long(b, c)
a = __bcd_add_long_long(b, c)
__bic_SR_register(unsigned short); - kasuje wskazane w danej bity rejestru SR
__bic_SR_register_on_exit(unsigned short); - kasuje wskazane w danej bity rejestru
SR przed
powrotem funkcji typu interrupt
lub monitor
__bis_SR_register(unsigned short); - ustawia wskazane w danej bity rejestru SR
__bis_SR_register_on_exit(unsigned short); - ustawia wskazane w danej bity rejestru
SR przed
powrotem funkcji typu interrupt
lub monitor __disable_interrupt();
- blokuje przerwania
__enable_interrupt();
- odblokowuje przerwania
unsigned short __even_in_range(adr, zakr) - przekazuje wartość ze wskazanego
adresu i
podanym zakresie. Dla funkcji
switch przy
obsłudze przewań
__get_interrupt_state(void):
- pobiera stan przerwań
__istate_t s = __get_interrupt_state();
__disable_interrupt();
/* ..................... */
__set_interrupt_state(s);
unsigned short __get_R4_register(void);
- zwraca zawartość rejestru
R4*
unsigned short __get_R5_register(void);
- zwraca zawartość rejestru
R5*
unsigned short __get_SP_register(void); - zwraca zawartość rejestru SP
unsigned short __get_SR_register(void); - zwraca zawartość rejestru SR
unsigned short __get_SR_register_on_exit(void); - zwraca zawartość rejestru SR w
funkcjach
interrupt i monitor
void __low_power_mode_n(void);
- wprowadza tryb oszczędnościowy n (0 -
4)
void __low_power_mode_off_on_exit(void); - wyprowadza procesor z trybu
oszczędnościowego przy
wyjściu z procedury
obsługi przerwania
void __set_interrupt_state(__istate_t);
- odtwarza stan przerwań
void __set_R4_register(unsigned short);
- wpisuje daną do rejestru R4*
void __set_R5_register(unsigned short); - wpisuje daną do rejestru R5*
* Tylko dla zablokowanych rejestrów
void __no_operation(void);
- rozkaz nop
void __set_SP_register(unsigned short);
- wpisuje daną do rejestru SP
unsigned short __swap_bytes(unsigned short);
- zamiana połówkowa danej
a = __swap_bytes(0x1234);
//a = 3412
Plik źródłowy
*.C
Plik źródłowy
*.s43
Kompilator
Asembler
Linke
r
Symulator
Debugger
W kompilatorze (ale tylko dla procesorów MSP430X) można uzyskać
domyślny dostęp do pamięci poprzez wybór modelu danych, co nie
przeszkadza na ulokowanie wybranej danej w dowolnym miejscu pamięci.
Modele pamięci:
Small – określa segment data16 co oznacza pierwszych 64 KB pamięci.
Dostęp do
pełnego obszaru 1MB jest tylko poprzez użycie funkcji
intrinsic.
Medium – wyznacza segment data16 jako domyślny i tam są lokowane
dane, ale
nie ogranicza to dostępu do całej pamięci 1MB.
Large – określa segment data20 jako pamięć domyślną, co oznacza dostęp
do całej
pamięci.
Model pamięci może być wybierany opcją --data_model
Zmiana modelu dla wybranej danej lub danych może nastąpić poleceniem
#pragma type_attribute
__data20 int i, j;
int __data20 k, l;
typedef char __data20 Byte;
typedef Byte *BytePtr;
Byte b;
BytePtr bp;
Odpowiada:
__data20 char b;
char __data20 *bp;
Wskaźniki:
int __data20 * p;
//deklaracja wskaźnika do zmiennej
umieszczonej w
//zakresie 1MB. Położenie
wskaźnika nie jest określone.
char __data16 * __data20 p2;
//wskaźnik jest umieszczony w obszarze
1MB a
//zmienna w obszarze 64 KB.
struct MyStruct
{
int alpha;
int beta;
};
__data20 struct MyStruct gamma;
//zmienna gamma jest strukturą
umieszczoną w
//1 MB pamięci.
Zła deklaracja:
struct MySecondStruct
{
int blue;
__data20 int green;
//błąd. Cała struktura musi być w tym
samym
//obszarze pamięci
};
int a;
//zmienna deklarowana w pamięci domyślnej określonej
przez
//model pamięci
int __data16 b; //zmienna w obszarze 64 KB
__data20 int c; // zmienna w obszarze 1 MB
int * d;
//wskaźnik w pamięci domyślnej do zmiennej typu int w
pamięci
//domyślnej.
int __data16 * e;
// wskaźnik w pamięci domyślnej do zmiennej typu
int w pamięci
// 64 KB
int __data16 * __data20 f;
// wskaźnik w pamięci 1 MB do zmiennej w
pamięci 64 KB int __data20 * myFunction(int __data16 *);
//deklaracja funkcji z parametrem który jest wskaźnikiem
zmiennej
//int w pamięci 64 KB. Funkcja zwraca wskaźnik do
zmiennej int w
//pamięci 1 MB.
C++
//deklaracja klasy umieszczona w pliku nagłówkowym:
class MyClass
{
public:
int alpha;
int beta;
__data20 static int gamma;
};
//potrzebna deklaracja w pliku źródłowym:
__data20 int MyClass::gamma;
// deklaracja zmiennej:
__data16 MyClass delta;
Zmienne deklarowane wewnątrz funkcji nazywane auto zmiennymi są
umieszczane w rejestrach lub na stosie. Zmienne te są aktualne tylko na
czas wykonywania funkcji
Funkcje obsługi przerwań dla modelu Small chowają na stos tylko 16 bitów
z 20 bitowych rejestrów. Można to zmienić poleceniem __save_reg20
Lokowanie zmiennych globalnych i statycznych pod adresami
absolutnymi.
Dwie możliwości:
- operator @
- dyrektywa #pragma location
Dane muszą być zadeklarowane jako no_init i/lub const.
Jeżeli dana jest deklarowana jako const to musi być inicjalizowana chyba
że jest deklarowana jako __no_init.
Lokowanie zmiennej pod adresem absolutnym wymaga by argument
operatora @ lub dyrektywy #pragma location liczbą wskazującą na
określony adres. Adres musi być zgodny z typem deklarowanej zmiennej.
Uwaga: Definicja zmiennej lokowanej pod adresem absolutnym może
być umieszczona w pliku nagłówkowym który może być dołączany do
każdego modułu wykorzystującego tę zmienną. Wystarczy normalna
deklaracja extern. Niewykorzystana w module definicja będzie
ignorowana.
W C++ zmienne const są typu static (w lokalnych modułach). Oznacza to,
że każdy moduł, w którym deklaruje się pewną liczbę zmiennych typu
const będzie zawierał oddzielne zmienne o tej samej nazwie. Jeżeli
nastąpi linkowanie kilku takich modułów zawierających , poprzez plik
nagłówkowy, deklaracje:
volatile const __no_init int x @ 0x100;
linker zgłosi, że jest więcej jak jedna zmienna ulokowana pod adresem
0x100. problem ten można rozwiązać w C i C++ poleceniem extern:
extern volatile const __no_init int x @ 0x100;
extern upublicznia zmienną x.
__no_init volatile char alpha @ 0x0200;
Zmienna deklarowana poleceniem __no_init jest lokowana pod stałym
adresem. Ułatwia to komunikacje pomiędzy różnymi modułami, procesami
itp.
#pragma location=0x0202
__no_init const int beta;
//bez inicjalizacji
const int gamma @ 0x0204 = 3;
//z inicjalizacją
Deklaracje z błedami:
int delta @ 0x0206;
//brak "__no_init" ,lub "const"
__no_init int epsilon @ 0x0207;
//zły adres – dla int powinien by
parzysty
Lokowanie funkcji pod wskazanym adresem:
void f(void) @ "FUNCTIONS";
void g(void) @ "FUNCTIONS"
{
}
#pragma location="FUNCTIONS"
void h(void);
Dla zmiany domyślnego obszaru pamięci można użyć deklaracji:
__data20 void f(void) @ "FUNCTIONS";
Deklaracje asemblera
Poniższy przykład pokazuje tablicę adresów do podprogramów:
NAME table
RSEG CONST
table DW addsubr, subsubr, clrsubr
RSEG CODE
addsubr ADD R4,R5
RET
subsubr SUB R4,R5
RET
clrsubr CLR R4
RET
END