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