1
Architektura Komputerów i Systemy
Operacyjne
Laboratorium 1
Ćwiczenie wstępne
Autor mgr inŜ. Paweł Wujek
Data 2.10.2011
2
1. Wstęp
Celem niniejszego laboratorium jest zapoznanie studenta z realizacją projektu
wykorzystującego mikrokontroler rodziny STM32 z rdzeniem Cortex – M3.
Niniejsza instrukcja przeprowadzi czytelnika przez proces tworzenia nowego projektu,
zapozna z najczęściej uŜywanymi funkcjami środowiska uruchomieniowego oraz przedstawi
sposób realizacji prostego programu polegającego na zapaleniu diody LED na płycie
uruchomieniowej.
Czytelnik zapozna się z pisaniem prostego programu w języku C, asembler, oraz programu
napisanego w C ze wstawkami asemblerowymi.
2. Tworzenie nowego projektu
Projekty tworzone będą z wykorzystaniem środowiska programistycznego
Keil µVision.
Aby uruchomić środowisko naleŜy dwukrotnie kliknąć na ikonę
znajdującą się na
pulpicie.
Uruchomiony program powinien wyglądać następująco
3
Jeśli program otworzy się i będzie widoczny poprzednio wykorzystywany projekt naleŜy go
zamknąć wybierając PROJECT > Close Project.
1. Następnie naleŜy utworzyć nowy projekt wybierając PROJECT > New µVision Project.
Pojawi się okno, w którym naleŜy wpisać ścieŜkę i nazwę projektu.
Jako ścieŜkę proszę wybrać C:\AKSIO_LAB\grupa\LAB1
Nazwa projektu lab1.
2. Po wybraniu zapisz pojawi się okno wyboru procesora.
NaleŜy wybrać firmę STMicroelectronics a model procesora STM32F103VB.
4
3. Po zatwierdzeniu pojawi się pytanie, czy skopiować STM32 Startup Code do folderu
projektu. NaleŜy wybrać NIE.
Plik zwierający Startup Code (startup_stm32f10x_md.s) naleŜy dołączyć do projektu.
4. Z prawej strony ekranu w oknie Project naleŜy rozwinąć pole Target 1, następnie kliknąć
prawym przyciskiem myszy na Source Group 1 i wybrać Add Files to Gruop…..
Po wybraniu pliku startup_stm32f10x_md.s (plik ten znajduje się w katalogu z instrukcją)
naleŜy nacisnąć przycisk Add i Close.
5. Następnie wybrać File>New otworzy się pole tekstowe, gdzie naleŜy wprowadzać kod
programu. Następnie naleŜy zapisać utworzony plik wybierając File>Save i nadając mu
nazwę main.c
Plik
main.c
naleŜy
dołączyć
do
projektu
w
sposób
analogiczny
do
pliku
startup_stm32f10x_md.s.
5
6. Kolejnym krokiem jest wybór programatora. W tym celu naleŜy wybrać
Flash>Configure Flash Tools…
Wybrać zakładkę Utilities. Zaznaczyć na niej Use Target Driver…..
Oraz wybrać ST-Link Debugger.
7. Następnie wybrać zakładkę Debug
Zaznaczyć na niej USE: i wybrać w menu rozwijanym ST-Link Debuger.
Na koniec zatwierdzić przyciskiem OK.
6
MoŜna równieŜ sprawdzić działanie programu bez wykorzystania płyty uruchomieniowej. Do
tego celu moŜna wykorzystać symulator. UmoŜliwia on sprawdzanie zawartości wszystkich
rejestrów dostępnych w procesorze.
Aby uruchomić symulator naleŜy w poprzednim oknie zaznaczyć po prawej stronie USE
Simulator.
3. Kompilacja i uruchamianie pierwszego programu
W niniejszym rozdziale student zostanie przeprowadzony przez proces pisania pierwszego
programu, kompilację, debugging oraz jego uruchomienie na płycie uruchomieniowej.
An początek naleŜy wpisać poniŜszy kod programu w oknie edytora.
#include "stm32f10x.h"
int main(void)
{
while(1)
{
}
}
Linia pierwsza pozwala na dołączenie pliku nagłówkowego dostarczanego przez producenta
mikroprocesora. Plik ten zawiera zdefiniowane nazwy wszystkich rejestrów, które będą
uŜywane w kolejnych przykładach.
7
Pliki nagłówkowe udostępnione przez środowisko Keil uVision wymagają wskazania rodziny
procesorów, której uŜywamy. Procesor dostępny w laboratorium naleŜy do rodziny „Medium
density devices”. Wyboru rodziny moŜna dokonać poprzez wybranie
Flash>Configure Flash Tools…
A następnie w zakładce C/C++ w polu Define wpisać STM32F10X_MD.
Co przedstawiono na rysunku
Na koniec naleŜy zatwierdzić przyciskiem OK.
Następnie naleŜy skompilować program naciskając przycisk Rebuild all target files. W ten
sposób tworzony jest plik wynikowy typu hex. Nie jest konieczne zapisywanie projektu, jest
on zapisywany automatycznie przed kompilacją:
Ewentualne błędny kompilacji moŜna obserwować w oknie Build Output znajdującym się na
dole ekranu.
Po pierwszej kompilacji okno powinno wyglądać podobnie jak na rysunku poniŜej
8
W przypadku braku błędów moŜna wgrać oprogramowanie do pamięci mikrokontrolera, w
tym celu naciśnij przycisk Start/Stop Debug Session
Po naciśnięciu przycisku w oknie edycji programu pokazywany jest plik po deasemblacji,
zauwaŜ w jaki sposób są kodowane rozkazy i ich argumenty
Przydatne funkcje debuggera
Pierwsza ikona z lewej na powyŜszym rysunku słuŜy do resetowania programu w procesorze.
Kolejna ikona umoŜliwia uruchomienie programu.
Trzecia słuŜy do zatrzymania działającego programu. (staje się aktywna w momencie
uruchomienia programu)
Kolejna grupa ikon przeznaczone jest do obsługi pracy krokowej programu. UmoŜliwiają one
wykonywanie instrukcji krok po kroku, lub przejście do kolejnej pułapki.
9
W momencie pisania programu lub po wejściu w tryb debuggera moŜliwe jest dodawanie
„pułapek”, w których program w czasie pracy zatrzyma się. Dodawanie pułapek polega na
dwukrotnym kliknięciu w szare pole po lewej stronie interesującej nas linii kodu. Poprawnie
ustawiona pułapka wygląda następująco:
Kolejną
przydatną
funkcja
jest
moŜliwość
podglądania
zawartości
rejestrów
odpowiedzialnych za pracę poszczególnych peryferiów.
Przykładowo aby zobaczyć rejestry odpowiedzialne za pracę portu A procesora naleŜy
wybrać Peripherals>General Purpose I/O>GPIOA. Aktywne stanie się wtedy następujące
okno
4. Przykładowy program
4.1 Pierwszy program w C
W niniejszym rozdziale zostanie zaprezentowany prosty program prezentujący sposób
zapalenia diody LED.
Na wstępie proszę wpisać następujący kod i uruchomić program.
#include "stm32f10x.h"
int main(void)
{
10
unsigned int licznik=0;
RCC->APB2ENR=0x00000008;
GPIOB->CRH=0x33333333;
while(1)
{
GPIOB->ODR=0x00000000;
for(licznik=1000000;licznik>0;licznik--);
GPIOB->ODR=0x00000100;
for(licznik=1000000;licznik>0;licznik--);
}
}
Efektem poprawnie uruchomionego programu będzie cykliczne zapalenie i zgaszenie
diody LED1.
Kolejnym zadaniem jakie naleŜy wykonać to zapalanie co drugiej dioda LED w pętli
(maja być zapalone diody 1,3,5,7 a potem 2,4,6,8).
Pierwszym
krokiem
jest
skonfigurowanie
wszystkich
rejestrów
zegarowych
mikroprocesora.
Dokumentacja
mikroprocesora
znajduje
się
w
katalogu
C:\AKSIO_LAB\PDF w pliku o nazwie stm32f10xxx.pdf.
PoniewaŜ zadaniem jest tylko zapalanie i gaszenie diody LED nie musimy konfigurować
źródeł sygnału zegarowego, ani jego częstotliwości poniewaŜ ustawienia domyślne są
wystarczające dla tego zadania. (Domyślnie wybrany jest wewnętrzny zegar o
częstotliwości 8MHz).
Przed konfiguracją pryferiów konieczne jest włączenie sygnału zegarowego taktującego
dany układ peryferyjny. W naszym przypadku konieczna będzie konfiguracja rejestru
APB2ENR. Aby tego dokonać naleŜy wpisać w kodzie następująca linię
RCC->APB2ENR = 0x00000000;
W miejsce zer podstawiając odpowiednią wartość. NaleŜy zapoznać się ze słowem
konfiguracyjnym tego rejestru. W naszym przypadku naleŜy wpisać 0x00000008.
Dlaczego? Odpowiedz powinna być jasna po dalszej konfiguracji.
11
Następnym krokiem jest konfiguracji portów IO. NaleŜy zacząć od znalezienia na
schemacie płyty uruchomieniowej (schematy dostępne są na końcu instrukcji) diody LED.
NaleŜy następnie śledząc drogę połączeń odnaleźć układ do którego połączone są diody.
Z powyŜszego schematu moŜna zauwaŜyć, Ŝe sygnał sterujący diodą D1 „przechodząc”
przez układ 74LVD541 (układ buforujący) otrzymuje nazwę PB8. Nazwy tej naleŜy
szukać na liniach podłączonych do innych układów.
Sygnał PB8 został podłączony do mikroprocesora STM32F103VBT6 do portu PB8.
Czytając w dokumentacji procesora rozdział dotyczący obsługi portów I/O (rozdział 8)
znajdziemy w nim informację dotyczące funkcjonowania GPIO(General Purpose IO).
Znajdują się wśród nich schematy elektryczne portów, informacje na temat trybów pracy
oraz konfiguracji.
Na początek naleŜy wypisać nazwy wszystkich rejestrów oraz zastanowić się jakie jest ich
przeznaczenie i sposób wykorzystania.
W naszym przykładzie została wpisana linia
12
GPIOB->CRH = 0x33333333;
Dlaczego akurat taka konfiguracja? Proszę się zastanowić.
Po poprawnym skonfigurowaniu portu GBPIOB naleŜy ustawić określoną wartość na jego
wyjściu. Wiemy, Ŝe chcąc zapalić diodę D1 musimy wystawić jedynkę na bicie 8 portu.
MoŜna to zrobić na jeden z poniŜszych sposobów.
GPIOB->ODR = 0x00000100;
GPIOB->BSRR=0x00000100;
Natomiast zero moŜna ustawić na jeden z następujących sposobów:
GPIOB->ODR = 0x00000000;
GPIOB->BSRR=0x01000000;
GPIOB->BRR = 0x00000100;
Proszę zastanowić się nad róŜnicami oraz zaletami i wadami powyŜszych sposobów.
4.2 Pierwszy program napisany w języku asembler
Na początku naleŜy utworzyć nowy projekt tak samo jak poprzednio jednak w punkcie 4
naleŜy wpisać nazwę main.asm
Po punkcie 7 naleŜy wybrać zakładkę Target i zaznaczyć opcję Use MicroLIB.
WaŜne:
W języku asembler kaŜde polecenie naleŜy pisać w nowej linii poprzedzając je spacją.
Etykiety naleŜy pisać od nowej linii.
Komentarze rozpoczynają się po średniku.
13
Na początek w oknie kodu programu naleŜy wpisać:
AREA MAINE, CODE, READONLY
EXPORT __main
Pierwsza i druga linia rozpoczyna się ze spacją.
Linie te odpowiadają za poinformowanie kompilatora o miejscu rozpoczynania się głównej
funkcji programu (main)
Następnie naleŜy wpisać etykietę funkcji głównej:
__main
MoŜna przejść teraz do pisania właściwego kodu programu.
Podobnie jak w języku C na początek naleŜy włączyć zegar na port B.
Poleceniu RCC->APB2ENR=0x00000008; w asemblerze odpowiada zestaw poleceń:
LDR R0, =0x40021018
LDR R1,=0x00000008
STR R1,[R0]
Pierwsza linia powoduje wpis do rejestru R0 wartości 0x40021018. Jest to adres rejestru
APB2ENR w przestrzeni adresowej.
Druga linia powoduje wpis do rejestru R1 wartości 0x00000008. Jest to wartość jaka naleŜy
wpisać do rejestru APB2ENR.
Trzecia linia powoduje wpis do pamięci pod adres z rejestru R0 wartości z rejestru R1.
Analogicznie poleceniu z języka C:
GPIOB->CRH=0x33333333;
W asemblerze odpowiada:
LDR R0, =0x40010C04
LDR R1, =0x33333333 ;
STR R1,[R0]
Wywołanie funkcji o nazwie DEL z parametrem w języku asembler wygląda następująco.
Parametr zapisany jest w rejestrze R2
LDR R2, =0x1E8480
14
BL DEL
Bezwzględny skok do etykiety (nazwa etykiety loop) realizowany jest za pomocą polecenia:
B loop
Funkcja odmierzająca czas zrealizowana jest poprzez zmniejszanie o jeden wartości zapisanej
w rejestrze R2 a następnie sprawdzanie, czy jest to juŜ wartość zero. Jeśli nie to następuje
skok do etykiety DEL. Realizuje to następujący fragment kodu:
DEL
NOP
SUBS R2, #1
BNE DEL
BX lr
Polecenie NOP dla procesora oznacza pustą instrukcję. Zabiera ona czas oraz powoduje
zwiększenie licznika rozkazów. Nie powoduje Ŝadnych inny efektów.
Polecenie SUBS R2, #1 powoduje odjęcie od wartości w rejestrze R2 liczby 1 oraz ustawienie
flagi informującej czy wynik odejmowania jest równy zero.
Polecenie BNE DEL sprawdza, czy flaga ustawiana przez polecenie SUBS jest ustawiona.
Jeśli nie jest, następuje skok do etykiety DEL. Jeśli jest ustawiona następuje przejście do
kolejnej linii kodu.
Polecenie BX lr odpowiada za skok do miejsca z którego została wywołana funkcja.
W pełni funkcjonalny kod wygląda następująco:
AREA MAINE, CODE, READONLY
EXPORT __main
;wywolanie glownej funkcji main
__main
; RCC->APB2ENR=0x00000008;
LDR R0, =0x40021018
LDR R1, =0x00000008
STR R1,[R0]
15
; GPIOB->CRH=0x33333333;
LDR R0, =0x40010C04
LDR R1, =0x33333333
STR R1,[R0]
loop ;petla zapalaja i gaszaca diode
;GPIOB->ODR=0x00000000;
LDR R0, =0x40010C0C
LDR R1, =0x00000000
STR R1,[R0]
;wywolanie funkcji odmierzajacej czas z parametrem zapisanym w R2
LDR R2, =0x1E8480
BL DEL
;GPIOB->ODR=0x00000100;
LDR R0, =0x40010C0C
LDR R1, =0x00000100
STR R1,[R0]
;wywolanie funkcji odmierzajacej czas z parametrem zapisanym w R2
LDR R2, =0x001E8480
BL DEL
;skok do poczatku petli
B loop
;funkcja odmierzajaca czas z parametrem R2
DEL
NOP
SUBS R2, #1
BNE DEL
BX lr
;koniec funkcji odmierzającej czas
16
NOP
END
4.3 Pierwszy program napisany w języku C z wstawką
asemblerową
Po napisaniu pierwszego programu w C i w asemblerze widzimy, Ŝe duŜo łatwiej i przyjaźniej
pisze się programy z wykorzystaniem języka C. Czasem jednak zdarza się, Ŝe chcemy mieć
pełna kontrolę nad sprzętem lub maksymalnie przyspieszyć wykonywanie instrukcji. W takim
przypadku moŜemy skorzystać z wstawek asemblerowych w języku C.
Przykładowo w naszym kodzie napisanym w C linię
RCC->APB2ENR=0x00000008;
MoŜemy zastąpić wstawką asemblerową.
W miejsce tej linii naleŜy wpisać wywołanie funkcji
funkcja_asm();
Teraz naleŜy napisać funkcję która będzie wywoływana. Wygląda ona następująco:
__asm funkcja_asm(void)
{
LDR R0, =0x40021018
LDR R1, =0x00000008
STR R1,[R0]
BX lr
NOP
}
Działanie poszczególnych poleceń asemblerowych wykorzystanych w funkcji jest zapewne
dobrze znana.
17
Polecenie BX lr dodane jest po to aby po zakończeniu operacji asemblerowych powrócić do
miejsca skąd funkcja została wywołana.
Polecenie NOP dodane jest w celu wyeliminowania ostrzeŜenia kompilatora.
Ostatecznie cały program napisany w C z wstawką asemblerową wygląda następująco:
#include "stm32f10x.h"
__asm funkcja_asm(void)
{
LDR R0, =0x40021018
LDR R1, =0x00000008
STR R1,[R0]
BX lr
NOP
}
int main(void)
{
unsigned int licznik=0;
funkcja_asm();
GPIOB->CRH=0x33333333;
while(1)
{
GPIOB->ODR=0x00000000;
for(licznik=1000000;licznik>0;licznik--);
GPIOB->ODR=0x00000100;
for(licznik=1000000;licznik>0;licznik--);
}
}
Teraz naleŜy samodzielnie wykonać zadanie postawione na początku ćwiczenia.
18
NaleŜy zapalić tylko diody D1, D3, D5, D7, następnie tylko diody D2, D4, D6, D8.
Do tego celu moŜna wykorzystać jeden z powyŜej przedstawionych programów.
19
5. Schematy elektryczne płyty bazowej
20
21
6. Polecenia języka Asembler
22
23