Wydział Informatyki, Elektroniki i Telekomunikacji Katedra Elektroniki Systemy akwizycji i transmisji sygnałów Laboratorium 1 Programowa obsługa interfejsu SPI w mikrokontrolerach STM32 Kraków, 2012 r. 1
Spis treści 1. Opis mikrokontrolera STM32F103RBT6... 3 2. Wykorzystywane płytki... 4 3. Template w Ride7... 7 4. Miganie diodą LED... 10 5. Przełączanie diody LED z wykorzystaniem Timera... 13 6. Komunikacja SPI z przetwornikiem cyfrowo-analogowym... 15 Cel Ćwiczenia: Napisanie programu mikrokontrolera STM32F103RBT6 w języku C, który cyklicznie co 1 ms będzie zwiększał o 1LSB napięcie na wyjściu przetwornika cyfrowo-analogowego. Po doliczeniu do maksymalnej wartości, przetwornik będzie rozpoczynał od zerowego napięcia na jego wyjściu pojawi się sygnał piłokształtny. Czas 1ms będzie odmierzał timer. Wymagane umiejętności: - czytanie ze zrozumieniem; - obsługa komputera PC; - obsługa oscyloskopu. 2
1. Opis mikrokontrolera STM32F103RBT6 Mikrokontrolery z rdzeniem ARM w ostatnich latach stały się bardzo popularną rodziną kontrolerów. Najpopularniejsze rdzenie to ARM7, ARM9, Cortex-M3 i inne. W trakcie ćwiczeń laboratoryjnych wykorzystamy płytkę zawierającą mikrokontroler STM32F103RBT6. Do najważniejszych zalet tego mikrokontrolera można zaliczyć: - 32 bitowy rdzeń RISC o oznaczeniu ARM CORTEX-M3; - sprzętowe mnożenie i dzielenie w jednym takcie zegara; - maksymalna częstotliwość pracy 72MHz; - 128kB FLASH, 20kB SRAM; - tryby oszczędzania energii (Sleep, Stop, Standby); - układ RTC i BACKUP podtrzymywany zewnętrzną baterią; - 12 bitowy przetwornik ADC (16 multipleksowanych kanałów; maksymalnie 1MSps); - 7 kanałów DMA; 7 timerów (watchdog, independent watchdog, systick, rozbudowany TIM1, prostsze TIM2-4); - debugowanie przez JTAG; - wbudowane interfejsy: 2 x I 2 C, 3 x USART, 2 x SPI, CAN, USB 2.0 (full speed); - Zasilanie 2 3.6 V; - 51 pinów ogólnego przeznaczenia (47 pinów w typowej aplikacji); - programowanie przez USART1; - darmowe biblioteki i przykłady na stronie producenta; - dużo darmowych środowisk programistycznych; - cena około 30 zł brutto (jedna sztuka w TME bez uwzględnienia kosztu przesyłki). Dodatkowo w innych mikrokontrolerach tej rodziny można znaleźć: - interfejs FSMC do komunikacji z równoległymi pamięciami SRAM, FLASH, itp; - interfejs SDIO kart pamięci SD i Multimedia Card; - interfejs Ethernet; - wyższe częstotliwości pracy, większa pojemność pamięci programu i pamięci operacyjnej, itp. 3
2. Wykorzystywane płytki W trakcie zajęć wykorzystane zostaną dwie płytki: - płytka z mikrokontrolerem i układem stabilizacji napięć zasilających; - płytka z przetwornikiem cyfrowo-analogowym. Płytka mikrokontrolera zawiera układ zasilający wykorzystujący stabilizatory napięcia +5V oraz +3.3V. Schemat zasilacza przedstawiono na Rys.1. Rys. 1. Schemat układu stabilizacji napięć. Dioda D2 zabezpiecza układ przed uszkodzeniem w wyniku odwrotnego podłączenia zewnętrznego zasilacza. Gniazdo JP4 służy do wyboru trybu bootowania. W zależności od stanu logicznego na wyprowadzeniach BOOT0 i BOOT1/PB2 mikrokontroler może rozpocząć swoje działanie wykonując: - program zawarty w pamięci FLASH (napisany przez programistę); - wbudowany bootloader umożliwiający programowanie przez port szeregowy; - program zawarty w pamięci SRAM. Typowa aplikacja mikrokontrolera została przedstawiona na Rysunku 2. Jak widać wszystkie linie portów mikrokontrolera zostały zabezpieczone szeregowym rezystorem o wartości 330 Ω. Rezystor ten nie jest wymagany w niektórych przypadkach nawet szkodliwy. Oporniki zostały umieszczone na płytce by zabezpieczyć wyprowadzenia mikrokontrolera przed przypadkowym uszkodzeniem w trakcie ćwiczeń laboratoryjnych. Do mikrokontrolera dołączono dwa rezonatory kwarcowe, przy czym one też nie są wymagane. Rezonator 8MHz służy do wytworzenia głównej częstotliwości taktującej mikrokontroler. Gdybyśmy nie wlutowali tego rezonatora mikrokontroler mógłby być taktowany z wbudowanego generatora RC o częstotliwości 8MHz (ma mniejszą stabilność). Drugi rezonator 38 khz może taktować układ czasu rzeczywistego i też nie jest wymagany. Jego funkcję może przejąć wbudowany generator RC o częstotliwości 40 khz. Jeżeli nie zastosujemy zewnętrznych rezonatorów to mamy do dyspozycji 51 pinów mikrokontrolera. Ponieważ mikrokontroler ma rozbudowany układ resetu to do resetowania można wykorzystać zwykły przycisk tak jak to przedstawiono na rysunku. 4
Rys. 2. Typowa aplikacja mikrokontrolera STM32. Aby móc programować mikrokontroler przez interfejs szeregowy, wymagany jest układ konwersji napięć. Mikrokontroler można programować tylko przez USART 1. Rysunek 3 przedstawia wykorzystany układ i sposób jego dołączenia do mikrokontrolera. Na rysunku 3 przedstawiono też sposób dołączenia interfejsu USB do mikrokontrolera. 5
Rys. 3. Układy pośrednie zapewniające komunikację USB i szeregową. Na Rysunku 4 przedstawiono schemat płytki dołączanej do płytki z mikrokontrolerem. Na dodatkowej płytce znajduje się przetwornik cyfrowo-analogowy wraz z diodą i rezystorami podciągającymi sygnały na magistrali SPI do napięcia +5V. Rys. 4. Schemat dodatkowej płytki z przetwornikiem cyfrowo-analogowym. 6
3. Template w Ride7 Na potrzeby zajęć laboratoryjnych został przygotowany projekt w darmowym środowisku Ride7. Projekt ten będzie w trakcie ćwiczenia stopniowo rozbudowywany. 3.1 Dwukrotnie kliknij plik Test.rprj znajdujący się w katalogu D:\Zaoczni\Gr1\ Otworzy się okno programu Ride7 przedstawione na Rysunku 5. Rys. 5. Okno programu Ride7. W tym oknie możemy wyróżnić cztery pola. Pole oznaczone cyfrą 1 prezentuje informacje o plikach zawartych / dołączonych do projektu. Za pomocą tego pola możemy otworzyć pliki z kodem źródłowym. Pole oznaczone cyfrą 2 umożliwia zmianę ustawień kompilacji w dalszej części ćwiczenia nie będziemy z tego pola korzystać. Pole 3 prezentuje zawartość otwartego pliku źródłowego lub nagłówkowego. Za pomocą zakładek w górnej części tego pola możemy decydować który plik aktualnie przeglądamy / edytujemy. W polu numer 4 znajdziemy informacje o przebiegu kompilacji. 3.2 Korzystając z pola 1 wyświetlić zawartość pliku main.c. Po włączeniu zasilania mikrokontroler przechodzi do wykonywania funkcji main(void), której listing zawarto poniżej. Jako pierwsza zostaje wykonana funkcja RCC_Init() której celem jest odpowiednia konfiguracja sygnałów zegarowych mikrokontrolera. 7
Kolejna funkcja GPIO_PinRemapConfig powoduje odłączenie pinów mikrokontrolera od wbudowanego układu JTAG. Następnie wywoływana jest funkcja GPIO_Config() która przeprowadzi konfigurację wyprowadzeń mikrokontrolera. Obecnie funkcja GPIO_Config() jest pusta, w dalszej części ćwiczenia wpiszemy tam instrukcje konfigurujące wyprowadzenie dołączone do diody LED. Jako ostatnia zostaje wywołana funkcja InterruptConfig() która przeprowadza wstępną konfigurację przerwań. Następnie mikrokontroler przejdzie do wykonywania pustej nieskończonej pętli. Listing 1 główna pętla programu. int main(void) { ///////////// Hardware Init //////////// RCC_Init(); //Clocks and PLL init GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE); // Disable JTAG and SW GPIO_Config(); //GPIO Configuration InterruptConfig(); //Interrupt Configuration //////////// Main Timer ///////////////// //////////// SPI ///////////////// //////////// START ///////////// while (1) { } } Komentarza wymaga funkcja RCC_Init znajdująca się w pliku main.c tuż pod główną pętlą programu. W analizie funkcji przydatne będzie drzewo sygnału zegarowego przedstawione na rysunku 6. Funkcja RCC_Init powoduje: - deinicjalizację układu zegarowego; - uruchomienie generatora dołączonego do zewnętrznego rezonatora 8MHz; - konfigurację bufora pamięci FLASH (o więcej szczegółów można zapytać prowadzącego); - częstotliwość linii HCLK jest ustawiana na równą częstotliwości linii SYSCLK; - częstotliwość linii PCLK2 (taktującej część interfejsów) jest ustawiana na równą częstotliwości linii HCLK; - częstotliwość linii PCLK1 (taktującej wolniejsze interfejsy) jest ustawiana na dwa razy mniejszą niż częstotliwość linii HCLK; 8
- proszę zwrócić uwagę że zegar taktujący TIM3 (będziemy z niego korzystać) jest taktowany częstotliwością 2 x PCLK1 = 48 MHz; - pętla fazowa jest konfigurowana do pracy z sygnałem generowanym w oscylatorze 8MHz; pętla fazowa mnoży tę częstotliwość sześć razy uzyskując SYSCLK = 48MHz; - jeżeli nie uda się uruchomić generatora z zewnętrznym kwarcem, system jest konfigurowany do pracy z wbudowanym generatorem RC 8MHz; - na koniec zostaje dołączony sygnał zegarowy do układów odpowiedzialnych za wyprowadzenia mikrokontrolera (port A, B i C). Rys. 6. Dystrybucja układu zegarowego w mikrokontrolerze. 9
4. Miganie diodą LED 4.1 W górnej części pliku main.c dopisujemy nowe zmienne tak jak to przedstawia rysunek 7. Rys. 7. Zmienne wykorzystywane do konfiguracji wyprowadzenia dołączonego do diody LED oraz licznik pętli generującej opóźnienie. Zmienna GPIO_InitStructure zostanie wykorzystana do konfiguracji wyprowadzenia 1 portu C, natomiast zmienna LoopCount posłuży do budowy prostego licznika generującego opóźnienie. 4.2 Wewnątrz funkcji GPIO_Config w pliku main.c wpisujemy konfigurację pinu dołączonego do diody LED. Kod który należy wpisać przedstawia Rysunek 8. Początkowo wypełniamy pola struktury GPIO_InitStructure. Chcemy aby pin 1 zmieniał się z maksymalną częstotliwością 2 MHz i był pinem wyjściowym z możliwością ustawienia poziomu logicznego wysokiego i niskiego. Następnie konfigurujemy port C mikrokontrolera za pomocą przygotowanej struktury i ustalamy stan logiczny niski wyprowadzenia C1 dioda zgaszona. 10
Rys. 8. Konfiguracja wyprowadzenia 1 portu C mikrokontrolera. 4.3 Modyfikujemy główny program tak by co sekundę dioda zapalała się lub gasła W tym celu sprawdzimy aktualny stan wyprowadzenia. Jeżeli jest niezerowy, to wyzerujemy pin w przeciwny wypadku pin zostanie ustawiony w stan wysoki. Na koniec odczekamy 1 sekundę w pętli, czyli policzymy do 48 milionów. Przykładowy kod programu przedstawia Rysunek 9. Rys. 9. W głównej pętli programu przełączamy stan diody i czekamy około 1 sekundy. 11
4.4 Budujemy projekt W tym celu należy wybrać z menu Project Build Project. Sprawdzamy czy w polu 4 pojawił się komunikat Build successful. Jeśli nie to próbujemy znaleźć i poprawić błąd. 4.5 Zmieniamy położenie zworki (programowanie) na płytce mikrokontrolera i go resetujemy przyciskiem na płytce. Reset Run Programowanie Rys. 10. W zależności od położenia zworki, po resecie uruchomi się bootloader lub program wgrany do mikrokontrolera. 4.6 Z pomocą programu Flash Loader Demo programujemy mikrokontroler. Po uruchomieniu programu Flash Loader Demo wybieramy programowanie za pomocą portu szeregowego UART. Należy podać numer portu COM i kliknąć Next, Next, Next, wybrać opcję Download to device, wskazać plik D:\Zaoczni\Gr1\Output\Tests.hex do załadowania, Next, Finish. 4.7 Zmieniamy ustawienie zworki na płytce mikrokontrolera i go resetujemy. 4.8 Sprawdzamy czy napisany program działa prawidłowo. Jak widać przełączanie diody następuje po około 10 x dłuższym czasie niż zakładano. Problem tkwi w sposobie w jaki kompilator zrealizował inkrementację zmiennej w pętli bez debugera nie jesteśmy w stanie powiedzieć co tak naprawdę dzieje się 48 milionów razy podczas odmierzania czasu. Z tego powodu w następnym kroku ustawimy timer by odmierzał czas 1ms. Timer działa niezależnie od kompilatora / rdzenia mikrokontrolera. Jego zasada działania sprowadza się do zliczania taktów zegara. 12
5. Przełączanie diody LED z wykorzystaniem Timera 5.1 W pierwszym etapie przeprowadzimy konfigurację układu przerwań NVIC oraz timera TIM3 dopisując poniższy kod w pliku main.c Rys. 11. Konfiguracja TIM3 oraz przerwania zgłaszanego przez TIM3. W pierwszej linii włączamy układ TIM3 poprzez doprowadzenie do niego sygnału zegarowego. Następnie informujemy układ przerwań NVIC aby reagował na przerwania zgłaszane przez TIM3. Przerwaniu od timera 3 nadajemy priorytet 1, oraz pod priorytet 1. Na koniec odblokowujemy przerwanie od TIM3. Następnie konfigurujemy TIM3 aby zliczał w górę takty zegara. Gdy zliczy do wartości TIM_Period zostanie zgłoszone przerwanie. 5.2 Wyznacz odpowiednią wartość TIM_Period przy której przerwanie będzie generowane co 1 ms i ją wpisz (np. 24000). Aby wyznaczyć częstotliwość pobudzającą TIM3 skorzystaj z Rysunku 6. 5.3 W pliku stm32f10x_it.c od samej góry, tuż pod #include, dopisz extern vu32 LoopCount; Dzięki temu zmienna zdefiniowana w pliku main.c będzie widziana w pliku stm32f10x_it.c. 5.4 W pliku stm32f10x_it.c uzupełnij funkcję obsługi przerwania od TIM3 zgodnie z Rysunkiem 12. 13
Rys. 12. Funkcja obsługi przerwania. Poprzednio ustawiliśmy TIM3 by generował przerwanie co 1ms. Za każdym razem gdy TIM3 zgłosi przerwanie to wykona się funkcja TIM3_IRQHandler. W tej funkcji wyzerujemy flagę przerwania timera, zatrzymamy timer, wyzerujemy jego licznik po czym z powrotem uruchomimy. Dodatkowo w funkcji obsługi przerwania każemy zwiększyć o jeden zmienną LoopCount zadeklarowaną i wykorzystywaną w pliku main.c. 14
5.5 Modyfikujemy główną pętlę programu (w pliku main.c) zgodnie z Rysunkiem 13. Rys. 13. Główna pętla programu. Jak tylko zmienna LoopCount zostanie zwiększona w funkcji obsługi przerwania TIM3 do wartości 1000 (co będzie odpowiadać upływowi 1 sekundy) nastąpi przełączenie diody LED. 5.6 Zbuduj projekt i zaprogramuj mikrokontroler podobnie jak w punktach 4.4 4.8. 6. Komunikacja SPI z przetwornikiem cyfrowo-analogowym W prawdzie wykorzystanie interfejsu SPI jest równie proste jak konfiguracja timera, ale tym razem wykorzystamy gotowy kod programu. Dołożymy nową zmienną value przechowującą wartość napięcia do wygenerowania, skonfigurujemy układ SPI a następnie po odmierzeniu 1ms przez TIM3 zwiększymy wartość zmiennej value i wyślemy ją do przetwornika. 6.1 Dołącz do projektu plik spi.c z katalogu D:\Zaoczni\Gr1\User\src\. W tym celu w oknie programu Ride7 należy kliknąć prawym przyciskiem myszy na katalog User. Wybrać Add Item i wskazać plik. 6.2 Dołącz plik nagłówkowy spi.h w pliku main.h Wybierz z menu File Open With Source Editor. Wskaż plik main.h z katalogu D:\Zaoczni\Gr1\User\inc. Dopisz #include spi.h 6.3 W pliku main.c utwórz nową zmienną globalną typu u16 o nazwie value. W zmiennej value będziemy przechowywać aktualny stan przetwornika cyfrowo-analogowego. 15
6.4 W pliku main.c wywołaj funkcję konfigurującą interfejs SPI i zmodyfikuj główną pętlę tak by co 1ms zmienna value była inkrementowana i przesyłana do przetwornika. Na Rysunku 14 przedstawiono jedno z możliwych rozwiązań. Ponieważ oko ludzkie nie jest w stanie zauważyć przełączania diody LED co 1ms przełączanie diody zostało usunięte dla większej czytelności. Wywołujemy funkcję MCP4901_Set lub MCP4812_Set w zależności który przetwornik jest umieszczony w płytce. Rys. 14. Inicjalizacja interfejsu SPI oraz generowanie przebiegu piłokształtnego. Komunikacja w standardzie SPI wykorzystuje cztery przewody: - SCLK sygnał zegarowy generowany przez układ nadrzędny (master); układy podrzędne słuchają tego sygnału i synchronizują z nim swe działanie; - MOSI (Master Out Slave In) szyna danych po której następuje przesyłanie danych z układu nadrzędnego do układu podrzędnego; - MISO (Master In Slave Out) szyna danych po której następuje szeregowe przesyłanie danych z układu podrzędnego do układu nadrzędnego; - CE (Chip Enable) każdy z układów podrzędnych ma swój własny sygnał CE (pozostałe linie SCLK, MOSI, MISO są wspólne dla wszystkich urządzeń); w danej chwili tylko jeden CE ma stan niski, dzięki temu tylko jeden układ podrzędny komunikuje się z masterem. Transmisja na liniach MOSI i MISO przebiega równolegle, tzn. master jednocześnie wysyła dane do slave i odbiera od niego dane na drugiej linii. Zastosowany przetwornik nie przesyła danych do mastera więc linia MISO nie jest wykorzystywana. 16
W pliku spi.c znajdują się: - definicje pinów i portów mikrokontrolera wykorzystane do podłączenia przetwornika; - cztery makra odpowiedzialne za wygenerowanie lub zniesienie sygnału aktywacji danego układu slave; - definicje dwóch zmiennych wykorzystywanych do konfigurowania pinów wyjściowych mikrokontrolera oraz wbudowanego układu SPI; - prototypy dwóch funkcji wykorzystywanych tylko w pliku spi.c (gdyby zostały umieszczone w pliku spi.h można by wywoływać te funkcje na przykład w main.c); - funkcja SPI_DAC_Init, która przeprowadza konfigurację pinów mikrokontrolera, deaktywuje układy slave ustawiając stan wysoki na liniach CE oraz wywołuje funkcję MCP4901_SpiSet; - funkcja MCP4901_SpiSet przeprowadza konfigurację interfejsu SPI tak by współpracował z przetwornikiem cyfrowo-analogowym. Przetworniki MCP4901 i MCP4812 wymagają takiej samej konfiguracji interfejs SPI. Konfigurujemy interfejs SPI tak by transmisja zachodziła jednocześnie na obu liniach, mikrokontroler był układem nadrzędnym (master), dane na magistrali będą przesyłane paczkami po 8 bitów, w stanie nieaktywnym sygnał zegarowy będzie miał wartość logiczną LOW, slave będzie zatrzaskiwał dane na zboczu narastającym, aktywowanie układu podrzędnego będzie realizowane programowo, sygnał zegarowy na linii SCLK będzie miał częstotliwość PCLK2/16 = 3MHz, dane przesyłamy począwszy od bitu najstarszego do najmłodszego. - funkcja MCP4901 realizuje przesłanie 8 bitowej liczby do przetwornika MCP4901. Na początek ustawiany jest poziom niski na linii CS dołączonej do tego przetwornika a następnie przesyłamy dwa bajty i ustawiamy CS w stan wysoki (o szczegóły można zapytać prowadzącego). - funkcja MCP4812 realizuje przesyłanie 10 bitowej danej do przetwornika MCP4812. Zasada działania jest podobna do funkcji MCP4901; - funkcja SPI_FLASH_SendByte została zapożyczona z przykładu dostarczonego przez producenta mikrokontrolera. Funkcja przesyła do slave (i jednocześnie odbiera) jeden bajt w oparciu o testowanie flag. 6.5 Zbuduj projekt i zaprogramuj mikrokontroler podobnie jak w punktach 4.4 4.8. 6.6 Obejrzyj sygnał generowany przez przetwornik na oscyloskopie. W jakim zakresie zmienia się napięcie wyjściowe przetwornika? Jaki jest okres przebiegu? Dlaczego? 6.7 Zmodyfikuj program mikrokontrolera tak by generował sygnał trójkątny (po doliczeniu do wartości maksymalnej zmienna value jest dekrementowana, po doliczeniu do wartości minimalnej zmienna value jest inkrementowana). 6.8 Zmodyfikuj program mikrokontrolera tak by generował sygnał sinusoidalny (dołącz plik nagłówkowy math.h i skorzystaj z funkcji sin(float kąt_w_radianach). 17