Kurs programowania mikrokontrolerów Megawin (1)

Kurs programowania mikrokontrolerów Megawin (1)

W poprzednim wydaniu „Elektroniki Praktycznej” opublikowaliśmy projekt płytki ewaluacyjnej z mikrokontrolerem MG32F103RBT6. Tym razem – zgodnie z zapowiedzią – rozpoczynamy (pierwszy na świecie!) kurs podstaw programowania tych interesujących, budżetowych układów firmy Megawin. Zaczniemy rzecz jasna od przygotowania środowiska programistycznego oraz sprawdzenia poprawności komunikacji programatora MLink – zarówno z IDE, jak i z docelowym procesorem. Następnie uruchomimy dwa proste programy umożliwiające „ożywienie” płytki i weryfikację działania niektórych znajdujących się na niej obwodów „okołoprocesorowych”.

Autor dziękuje firmie Micros (www.micros.com.pl) za udostępnienie programatora MLink oraz próbek układu MG32F103RBT6 na potrzeby opracowania niniejszego kursu.

Instalacja środowiska programistycznego

Mikrokontrolery Megawin z rdzeniem ARM Cortex-M3 mogą być programowane przy użyciu środowiska Keil MDK, wyposażonego w odpowiednie pakiety biblioteczne.

Używana w niniejszym kursie wersja środowiska Keil MDK Community Edition jest przeznaczona tylko do celów prywatnych i ewaluacyjnych – nie może być stosowana w projektach komercyjnych.

W celu pobrania środowiska Keil MDK należy wejść na stronę: keil.arm.com/mdk-community, a następnie założyć darmowe konto, używając swojego prywatnego adresu e-mail, na który wysłany zostanie kod weryfikacyjny niezbędny do przejścia do krótkiego formularza danych osobowych. Następnie, wracając jeszcze raz na stronę: https://www.keil.arm.com/mdk-community/ (rysunek 1), należy pobrać plik instalatora (rysunek 2).

Rysunek 1. Strona internetowa umożliwiająca pobranie instalatora środowiska Keil MDK-Community edition
Rysunek 2. Link do pobrania pliku instalatora środowiska Keil MDK

Po uruchomieniu pozyskanego w opisany sposób pliku wykonywalnego, w oknie Pack Installer (rysunek 3) możemy wybrać interesujący nas procesor z listy znajdującej się po lewej stronie ekranu, a następnie zainstalować odpowiedni pakiet, klikając przycisk Install, znajdujący się tuż obok sekcji Device Specific → Megawin:CM3_DFP. Etap ten nie jest jednak konieczny – za chwilę bowiem i tak doinstalujemy najnowszą wersję bibliotek za pomocą specjalnego programu dostarczanego przez producenta.

Rysunek 3. Okno Pack Installer środowiska Keil MDK

Na razie konieczne jest natomiast uzyskanie odpowiedniej licencji – proces ten wymaga uruchomienia środowiska Keil z uprawnieniami administratora, a następnie wybrania opcji File → License Management. W otwartym w ten sposób oknie dialogowym należy kliknąć przycisk Get LIC via Internet (rysunek 4), po czym przekierowani zostaniemy do strony internetowej z jeszcze jednym formularzem, którego zatwierdzenie wygeneruje kod nowej licencji (LIC). Ów kod zostanie przesłany mailem na adres podany w formularzu. Po przekopiowaniu otrzymanego ciągu znaków w odpowiednie pole okna License Management należy kliknąć przycisk Add LIC – po chwili powinny zostać wyświetlone dane uzyskanej licencji MDK-ARM Community. Jeżeli tak się stanie – oznacza to, że program jest gotowy do pracy.

Rysunek 4. Okno License Management (dane wrażliwe ukryto)

Konfiguracja środowiska Keil

Aby pobrać dodatkowe sterowniki, przechodzimy na stronę: https://www.megawin.com.tw/product/MG32F103RBT6#Support i w zakładce Support klikamy link zatytuowany OCDM3 MLink (rysunek 5).

Rysunek 5. Strona firmy Megawin z linkami do pobierania materiałów, bibliotek i narzędzi programowych

Skompresowane archiwum zawiera szereg narzędzi programowych przeznaczonych do współpracy z oficjalnym programatorem/debuggerem sprzętowym MLink (fotografia 1): aplikację ICPM3_Programmer, biblioteki dynamiczne M3_MLink.dll i HexEdit.dll, pakiet bibliotek kompatybilny ze środowiskiem Keil (Megawin.CM3_DFP.1.3.0) oraz program, który najbardziej interesuje nas w tej chwili: SetupMLink_forKeil (rysunek 6) – po rozpakowaniu archiwum uruchamiamy zatem ostatni z wymienionych plików.

Fotografia 1. Widok programatora MLink wraz z nakładkami zabezpieczającymi (http://t.ly/JGLuk)
Rysunek 6. Zawartość folderu po rozpakowaniu paczki OCDM3 MLink

W otwartym okienku dialogowym wskazujemy ścieżkę instalacji Keila (domyślnie będzie to C:\Keil_v5), a następnie klikamy Install. Program nie tylko automatycznie przeprowadzi instalację sterownika programatora MLink, ale także umieści pakiet DFP (zestaw bibliotek, plików startowych itp.) w bazie środowiska Keil, dzięki czemu będziemy mogli od razu po zakończeniu konfiguracji przejść do tworzenia pierwszego programu. Przy okazji możemy także zaktualizować oprogramowanie układowe interfejsu MLink. Po podłączeniu urządzenia do portu USB komputera należy nacisnąć przycisk Update MLink Firmware for M3 – o postępach aktualizacji poinformuje nas pasek statusu na dole okna programu, a na koniec zostanie wyświetlony stosowny komunikat.

Tworzenie pierwszego projektu

Aby utworzyć projekt, można przejść do menu Project → New μVision Project…, a następnie ręcznie przeprowadzić cały proces konfiguracji opcji toolchaina, dodawania bibliotek i mozolnego ustawiania wszystkich pozostałych elementów. Na szczęście inżynierowie aplikacyjni z firmy Megawin wykonali lwią część pracy za nas, udostępniając kompletny pakiet wsparcia – w tym także gotowy szablon projektu, w pełni skonfigurowany zgodnie z wymogami środowiska Keil. Grzechem byłoby więc nie skorzystać z tego udogodnienia. Paczkę z niezbędnym repozytorium znajdziemy na podanej wcześniej stronie internetowej, tym razem jednak musimy wybrać link Sample Code – Cortex-M3 → MG32F10x. Archiwum warto umieścić w folderze o możliwie krótkiej ścieżce dostępu, a nazwę samej paczki dodatkowo skrócić, np. do postaci Megawin – wynika to z faktu, iż system Windows w niektórych przypadkach nie radzi sobie z wypakowaniem folderu skompresowanego (z uwagi na zbyt długie ścieżki dostępu niektórych znajdujących się w nim plików – ot, drobna pułapka, którą twórca słynnego EEVBlog nazwałby zapewne mianem „a trap for young players”).

Po rozpakowaniu i otwarciu archiwum ujrzymy folder zawierający cztery główne podkatalogi:

  • Libraries – zawierający bibliotekę CMSIS, sterownik podstawowych bloków peryferyjnych (MG32F10x_StdPeriph_Driver) oraz sterownik interfejsu USB (MG32F10x_USBDevice_Driver).
  • MG32F10x_Peripherals_Library_Manual – przechowujący dokumentację sterownika w formacie HTML.
  • Project – zawierający trzy kolejne podfoldery z prostym projektem na bazie systemu FreeRTOS, bogatym zestawem przykładowych projektów korzystających ze sterownika StdPeriph dla poszczególnych peryferiów MCU oraz to, co interesuje nas teraz najbardziej: szablon projektu (MG32F10x_StdPeriph_Template).
  • Utilities – zestaw dodatkowych plików źródłowych, m.in. z funkcjami ułatwiającymi wysokopoziomową obsługę interfejsu UART.

Pozornie najprostszą metodą na zduplikowanie szablonu byłoby przekopiowanie wspomnianego wcześniej folderu MG32F10x_StdPeriph_Template do wybranej przez nas lokalizacji i zmiana nazwy tak utworzonej kopii, np. na Projekt01. Ponieważ jednak w strukturze plików konfiguracyjnych projektu (tj. *.uvprojx oraz *.uvoptx, przechowujących wszystkie niezbędne ustawienia), podane są ścieżki względne do folderów bibliotecznych, taka operacja spowodowałaby, że Keil miałby problem ze znalezieniem odpowiednich plików. Dlatego też – dla ułatwienia – proponuję umieścić folder-kopię tuż obok oryginału. Wtedy wystarczy zmienić nazwę folderu oraz wspomnianych dwóch plików, podmieniając oryginalny ciąg znaków np. na Projekt01. Po wykonaniu tej operacji możemy otworzyć projekt, klikając dwukrotnie ikonę pliku Projekt01.uvprojx w oknie Eksploratora. Dla pewności warto spojrzeć na widoki zawartości poszczególnych folderów, zaprezentowane na rysunkach 7 i 8.

Rysunek 7. Struktura folderu po rozpakowaniu paczki z biblioteką StdPeriph
Rysunek 8. Zawartość folderu z projektem utworzonym na bazie szablonu (po zmianie domyślnych nazw plików)

Po otwarciu środowiska Keil powinniśmy skonfigurować kilka ustawień, które w domyślnej formie mogą uniemożliwić poprawną kompilację i uruchomienie projektu. Najpierw, używając przycisku znajdującego się na najważniejszym z naszego punktu widzenia pasku narzędziowym środowiska Keil (rysunek 9), otwieramy okno Options for Target, w którym wybieramy zakładkę Device.

Rysunek 9. Główny pasek narzędzi środowiska Keil MDK

Tutaj musimy zmienić domyślny model procesora z MG32F103C9T6 na MG32F103RBT6 (rysunek 10).

Rysunek 10. Widok zakładki Device okna Options for Target

Kolejna modyfikacja dotyczy wyboru kompilatora, którego użyjemy do zbudowania naszego projektu (zakładka Target). Znajdujący się w pobranym archiwum projekt został opracowany w wersji 5, zaś aktualna (w chwili pisania niniejszego artykułu) jest wersja 6 kompilatora (a dokładniej 6.21) – dlatego właśnie zmieniamy stosowne ustawienie (rysunek 11).

Rysunek 11. Widok zakładki Target okna Options for Target

Na kolejnej zakładce zatytułowanej Output (rysunek 12) możemy ponadto wpisać własną nazwę pliku wyjściowego – w celu zachowania porządku warto podać nazwę Projekt01.

Rysunek 12. Widok zakładki Output okna Options for Target

Bardzo ważne ustawienia czekają na nas także w zakładce C/C++ (AC6). Tutaj, w polu tekstowym Define (rysunek 13), możemy ustawić potrzebne wartości częstotliwości zegara systemowego oraz rezonatora kwarcowego, współpracującego z HSE. Ponieważ jednak na naszej płytce ewaluacyjnej rezonator XC1 ma częstotliwość 12 MHz, a w przypadku samego MCU chcemy pracować z domyślną, maksymalną częstotliwością 72 MHz, to możemy z czystym sumieniem pozostawić takie właśnie domyślne ustawienia szablonu. Warto natomiast przestawić poziom optymalizacji kompilatora na -O0.

Rysunek 13. Widok zakładki C/C++ (AC6) okna Options for Target

Po wykonaniu wszystkich opisanych powyżej operacji należy jeszcze przejść do konfiguracji interfejsu sprzętowego. W tym celu – w zakładce Debug – sprawdzamy, czy programator na pewno jest ustawiony na opcję MLink Cortex-M3 Debugger (rysunek 14). Należy przy tym zwrócić uwagę, by zaznaczone były opcje Load Application at Startup oraz Run to main().

Rysunek 14. Widok zakładki Debug okna Options for Target

Poprawność rozpoznania sprzętu możemy zweryfikować, klikając przycisk Settings – w oknie po prawej stronie powinniśmy zobaczyć kod ID oraz nazwę urządzenia (rysunek 15).

Rysunek 15. Widok okna ustawień interfejsu MLink, zakładka Debug

Jeżeli wszystko działa poprawnie, przechodzimy do zakładki Flash Download (rysunek 16).

Rysunek 16. Okno ustawień interfejsu MLink, zakładka Flash Download

W tym momencie należy upewnić się, że zaznaczone są opcje Erase Sectors, Program oraz Verify, a następnie zaznaczyć domyślnie wyłączoną funkcję Reset and Run. Koniecznie należy także sprawdzić, czy w dolnej części okna widoczny jest algorytm wgrywania kodu maszynowego do pamięci procesora (Programming Algorithm). Jeżeli tak nie jest – należy kliknąć przycisk Add, a następnie wybrać opcję odpowiadającą naszemu mikrokontrolerowi (rysunek 17).

Rysunek 17. Widok okna wyboru algorytmu programowania pamięci Flash procesora

Teraz możemy już przejść do edycji głównego pliku źródłowego, czyli main.c. W tym celu rozwijamy folder User w drzewie projektu, znajdującym się po lewej stronie okna środowiska Keil. Po dwukrotnym kliknięciu tej pozycji naszym oczom powinno ukazać się okno edytora z domyślnym kodem źródłowym.

Pierwszy program

Trudno wyobrazić sobie tutorial programowania mikrokontrolerów – niezależnie od tego, której platformy dotyczy – bez napisania pierwszego programu w postaci klasycznego „blinky”, czyli prostego migania diodą LED. Aby stało się zadość tradycji, w naszym kursie również zaczniemy od takiego właśnie zadania.

Zanim jednak przejdziemy do pisania właściwego kodu z użyciem instrukcji z biblioteki StdPeriph, na początek utwórzmy plik źródłowy z definicjami makr ułatwiających obsługę linii GPIO procesora. Na listingu 1 można zobaczyć stosowne oznaczenia portów oraz numerów ich linii, które odpowiadają za obsługę poszczególnych obwodów peryferyjnych w naszym zestawie ewaluacyjnym.

void initPeripherals(void){

/* Procedura inicjalizujaca glowne peryferia sprzetowe MCU */

/* Wlaczenie taktowania GPIO i szyny APB1 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |
RCC_APB1Periph_GPIOA |
RCC_APB1Periph_GPIOB |
RCC_APB1Periph_GPIOC |
RCC_APB1Periph_GPIOD, ENABLE);

/* Konfiguracja linii GPIO do obslugi diod LED – wyjscia niepodciagane do VCC/GND, niska predkosc */

GPIO_Init(LED1_port, LED1_pin, GPIO_MODE_OUT | GPIO_OTYPE_PP | GPIO_PUPD_NOPULL | GPIO_SPEED_LOW);
GPIO_Init(LED2_port, LED2_pin, GPIO_MODE_OUT | GPIO_OTYPE_PP | GPIO_PUPD_NOPULL | GPIO_SPEED_LOW);

}

Listing 1.

Środowisko Keil nie należy niestety do najbardziej intuicyjnych pod względem sposobu dodawania plików bibliotecznych – dlatego część pracy trzeba wykonać ręcznie. Na szczęście operacji tych jest stosunkowo niewiele – na początek utwórzmy dwa dodatkowe foldery w głównym katalogu naszego projektu (rysunek 18) i nazwijmy je następująco: inc oraz src.

Rysunek 18. Struktura folderu projektu po dodaniu podkatalogów inc i src

Jak nietrudno się domyślić, pierwszy z nich będzie przechowywał pliki nagłówkowe programu, drugi zaś – właściwe pliki z kodem źródłowym. Po utworzeniu katalogów trzeba jeszcze wskazać Keilowi, gdzie ma on szukać plików bibliotecznych – w tym celu uruchamiamy ponownie okno Options for Target, tym razem jednak dopisujemy nową ścieżkę dostępu w sposób opisany na rysunku 19.

Rysunek 19. Kolejność wykonywania operacji podczas dodawania folderu z plikami nagłówkowymi: (1) kliknij przycisk z wielokropkiem, znajdujący się po prawej stronie pola tekstowego Include na zakładce C/C++(AC6) okna Options for Target, (2) – kliknij przycisk New (Insert) znajdujący się w nowo otwartym oknie Folder Setup, (3) – kliknij przycisk z wielokropkiem po prawej stronie ostatniej linii znajdującej się na liście folderów. Po ostatniej z wymienionych czynności zostanie otwarte standardowe okno menedżera plików Windows, pozwalające na ręczne wskazanie ścieżki do folderu z plikami nagłówkowymi

Po zatwierdzeniu możemy już utworzyć pusty plik źródłowy, klikając prawym klawiszem myszy folder wirtualny User (w drzewie projektu), następnie wybierając opcję Add New Item to Group ‘User’, a w dalszej kolejności Header File (.h) i – na sam koniec – wpisując nazwę pliku (np. hwconfig.h) w polu Name (rysunek 20). Plik należy umieścić w folderze inc. Teraz możemy już przepisać (lub – w celu zaoszczędzenia czasu – wkleić) odpowiedni kod (zamieszczony w materiałach źródłowych do niniejszego artykułu) do otwartego w ten sposób, pustego pliku w oknie edytora IDE. Plik automatycznie pojawi się w drzewie projektu.

Rysunek 20. Okno dodawania nowego pliku nagłówkowego

W podobny sposób dodajemy jeszcze dwa pliki: jeden źródłowy (hardware.c) – który trafi do folderu src – oraz odpowiadający mu nagłówek (hardware.h) – w katalogu inc. W plikach tych będziemy umieszczać funkcje obsługi peryferiów sprzętowych mikrokontrolera (oraz deklaracje tychże funkcji) – na początek dodajmy zatem procedurę inicjalizującą linie GPIO odpowiedzialne za sterowanie diodami LED. Ciało funkcji pokazano na listingu 2.

#ifndef __HWCONFIG_H
#define __HWCONFIG_H

#include "mg32f10x.h"

/* diody LED1 i LED2 */

#define LED1_port GPIOB
#define LED1_pin GPIO_Pin_13

#define LED2_port GPIOB
#define LED2_pin GPIO_Pin_12

/* wyswietlacz LED */

#define DISP_A_port GPIOB
#define DISP_A_pin GPIO_Pin_7

#define DISP_B_port GPIOC
#define DISP_B_pin GPIO_Pin_11

#define DISP_C_port GPIOD
#define DISP_C_pin GPIO_Pin_2

#define DISP_D_port GPIOB
#define DISP_D_pin GPIO_Pin_4

#define DISP_E_port GPIOB
#define DISP_E_pin GPIO_Pin_5

#define DISP_F_port GPIOB
#define DISP_F_pin GPIO_Pin_6

#define DISP_G_port GPIOC
#define DISP_G_pin GPIO_Pin_12

#define DISP_DP_port GPIOB
#define DISP_DP_pin GPIO_Pin_3

#define DISP_COM1_port GPIOC
#define DISP_COM1_pin GPIO_Pin_7

#define DISP_COM2_port GPIOC
#define DISP_COM2_pin GPIO_Pin_8

#define DISP_COM3_port GPIOC
#define DISP_COM3_pin GPIO_Pin_9

#define DISP_COM4_port GPIOC
#define DISP_COM4_pin GPIO_Pin_6

/* wyjscie potencjometru */

#define POT_port GPIOC
#define POT_pin GPIO_Pin_4

/* interfejs UART */

#define UART_TX_port GPIOA
#define UART_TX_pin GPIO_Pin_9

#define UART_RX_port GPIOA
#define UART_RX_pin GPIO_Pin_10

/* interfejs USB */

#define USB_DM_port GPIOA
#define USB_DM_pin GPIO_Pin_11

#define USB_DP_port GPIOA
#define USB_DP_pin GPIO_Pin_12

/* interfejs I²C */

#define I²C_SCL_port GPIOB
#define I²C_SCL_pin GPIO_Pin_10

#define I²C_SDA_port GPIOB
#define I²C_SDA_pin GPIO_Pin_11

/* interfejs SPI */

#define SPI_CS_port GPIOC
#define SPI_CS_pin GPIO_Pin_0

#define SPI_MOSI_port GPIOC
#define SPI_MOSI_pin GPIO_Pin_3

#define SPI_MISO_port GPIOC
#define SPI_MISO_pin GPIO_Pin_2

#define SPI_SCLK_port GPIOC
#define SPI_SCLK_pin GPIO_Pin_1

/* przyciski */

#define SW1_port GPIOB
#define SW1_pin GPIO_Pin_0

#define SW2_port GPIOC
#define SW2_pin GPIO_Pin_5

#endif

Listing 2.

Osoby, które pracowały na mikrokontrolerach STM32 jeszcze przed erą HAL (tj. w czasie, gdy obowiązującym standardem była jeszcze biblioteka Standard Peripherals Library), zauważą nieprzypadkowe podobieństwo zastosowanych tutaj funkcji obsługujących bloki RCC i GPIO do tych znanych z STM32 – istotnie, ta część biblioteki przeznaczonej do mikrokontrolerów Megawin w dużej mierze bazuje właśnie na bardzo zbliżonej koncepcji. Warto jednak zwrócić uwagę, że większość pozostałych peryferiów ma częściowo lub nawet całkowicie inną konstrukcję niż odpowiadające im peryferia procesorów STM32F1, stąd bezpośrednie przeniesienie kodu projektów napisanych na STM32 będzie w znakomitej większości przypadków niemożliwe.

Garść wyjaśnień może być niezbędna osobom, które wcześniej nie miały styczności z programowaniem mikrokontrolerów STM32. Polecenie RCC_APB1PeriphClockCmd() z parametrem ENABLE ma za zadanie włączyć wewnętrzne obwody taktowania, które są niezbędne do uruchomienia bloków peryferyjnych – w tym przypadku portów od GPIOA do GPIOD, obsługiwanych przez wewnętrzną szynę APB1. Odpowiednie maski o postaci RCC_APB1Periph_GPIOx (gdzie x to oznaczenie portu A...D) są sumowane logicznie i tworzą pierwszy parametr funkcji RCC_APB1PeriphClockCmd(). Dodatkowo w sumie znalazła się także obligatoryjna flaga RCC_APB1Periph_BMX1, odpowiedzialna za uruchomienie taktowania szyny APB1 (Bus MatriX 1).

Druga zastosowana w tym fragmencie kodu funkcja z biblioteki StdPeriph ma za zadanie skonfigurować daną linię portu GPIO. W tym celu należy podać w wywołaniu funkcji GPIO_Init() trzy kolejne parametry:

  • wskaźnik na strukturę umożliwiającą dostęp do danego portu (typu GPIO_TypeDef* – np. GPIOA),
  • numer portu w postaci zgodnej z zapisem stosowanym w bibliotece (uwaga – nie stosujemy tutaj po prostu numeru określonej linii GPIO, ale używamy aliasu, przykładowo: aby odwołać się do PA8, podajemy parametr GPIO_Pin_8, który w istocie jest aliasem liczby 0x100, czyli… bitu o wartości 1 umieszczonego na pozycji 9),
  • wartość konfiguracyjną, w której poszczególne elementy (zsumowane logicznie) określają koleje parametry danej linii, np.:
    • GPIO_MODE_OUT – pin I/O pracuje jako wyjście cyfrowe ogólnego przeznaczenia,
    • GPIO_OTYPE_PP – wyjście jest typu push-pull,
    • GPIO_PUPD_NOPULL – nie stosujemy rezystorów podciągających do VCC ani GND,
    • GPIO_SPEED_LOW – linia będzie pracowała ze stosunkowo małą częstotliwością przełączania (ten parametr pozwala uzyskać optymalny balans pomiędzy poziomem zakłóceń EMI generowanych podczas przełączania a szybkością narastania i opadania zboczy sygnału).

Dla jeszcze większej wygody programowania naszego procesora napiszmy także prosty wrapper do obsługi znajdujących się na płytce ewaluacyjnej diod LED D1 i D2 oraz prostą funkcję opóźniającą – ciała obydwu procedur można zobaczyć na listingu 3.

void LED_onoff(uint8_t led, LED_state_t state){

/* Wrapper do obslugi diod LED */
/* led – numer diody (1 lub 2) */
/* state – nowy stan diody (LED_ON lub LED_OFF) */

if(led == 1){

if(state == LED_ON)
GPIO_ResetBits(LED1_port, LED1_pin);
else
GPIO_SetBits(LED1_port, LED1_pin);

}else if(led == 2){

if(state == LED_ON)
GPIO_ResetBits(LED2_port, LED2_pin);
else
GPIO_SetBits(LED2_port, LED2_pin);

}
}

void delay(uint32_t ms){

/* Prosta funkcja zatrzymujaca procesor na okolo 1 ms */

for(uint32_t i = 0; i < 3600; i++){
for(uint32_t j = 0; j < ms; j++) __NOP();
}

}

Listing 3.

Uzbrojeni w najważniejsze fragmenty kodu możemy w końcu przejść do głównej funkcji programu, zaprezentowanej na listingu 4.

int main(void)
{
/* Inicjalizacja peryferiow */
initPeripherals();

/* Wylaczenie obu diod LED */
LED_onoff(1, LED_OFF);
LED_onoff(2, LED_OFF);

while (1){

LED_onoff(1, LED_ON);
LED_onoff(2, LED_OFF);

delay(500);

LED_onoff(1, LED_OFF);
LED_onoff(2, LED_ON);

delay(500);

}
}

Listing 4.

Jeżeli wszystkie dotychczasowe etapy zostały wykonane poprawnie, możemy skompilować nasz pierwszy program, klikając przycisk Build lub naciskając klawisz funkcyjny F7. O prawidłowym zakończeniu budowania pliku binarnego świadczyć będzie komunikat w konsoli Build Output: „.\Objects\Projekt01.axf” – 0 Error(s), 0 Warning(s).

Podłączenie programatora do płytki i wgranie pierwszego programu

Prawidłowe podłączenie programatora MLink do złącza DBG płytki ewaluacyjnej pokazano na fotografii 2. Jak widać, skrajny „górny” pin wtyku goldpin interfejsu MLink (oznaczony literami CLK) należy pozostawić niepodłączony – dla ułatwienia w złączu DBG przewidziano odpowiadający mu styk, także niepodłączony do żadnego z sygnałów procesora. Wszystkie pozostałe linie są łączone w tej samej kolejności (w przypadku obydwu złączy). Do programowania można użyć kabla wielożyłowego (z izolacją wielokolorową lub jednobarwną) bądź zestawu pięciu przewodów żeńsko-żeńskich (z popularnymi wtykami typu DuPont/BLS).

Fotografia 2. Sposób podłączenia programatora do płytki ewaluacyjnej

Do zasilania płytki z powodzeniem wystarczy zastosować kabel mini USB, podłączony do jednego z wolnych portów USB komputera. Wszystkie ćwiczenia w ramach niniejszego kursu przewidziano w taki sposób, by nie było konieczne stosowanie zewnętrznego zasilacza – nawet podczas obsługi wyświetlacza LED zasilanie z portu USB, doprowadzone poprzez bezpiecznik polimerowy 100 mA, okazuje się w zupełności wystarczające. W przypadku korzystania z bardziej „prądożernych” modułów zewnętrznych (np. transceiverów GSM, Wi-Fi czy odbiornika GPS), które zasilanie pobierałyby ze złączy goldpin znajdujących się na płytce, należy oczywiście przełączyć zworkę VSEL na pozycję VEXT i podłączyć odpowiednie źródło energii do gniazda śrubowego VEXT.

Po zasileniu płytki i podłączeniu programatora jesteśmy w stanie wgrać skompilowany kod maszynowy do pamięci Flash mikrokontrolera – w tym celu klikamy przycisk Download lub naciskamy klawisz F8. Po chwili naszym oczom powinien ukazać się komunikat:

Erase Done.
Programming Done.
Verify OK.
Todo: HW Chip Reset after flash load
Application running…
Flash Load finished at (...)

Ponieważ w opcjach środowiska Keil zaznaczyliśmy opcję resetowania procesora po zakończeniu ładowania kodu do pamięci Flash, w tym momencie powinniśmy już obserwować naprzemienne miganie obu diod D1 i D2.

Obsługa wyświetlacza LED

Na koniec tej części kursu rozbudujmy jeszcze nasz program o obsługę wyświetlacza siedmiosegmentowego. Dla uproszczenia całość procedury multipleksowania zrealizujemy w nieszczególnie efektywny sposób blokujący – w tym momencie chodzi bowiem jedynie o przetestowanie poprawności montażu wyświetlacza, współpracujących z nim elementów dyskretnych oraz scalonego sterownika low-side, kontrolującego pracę segmentów (katod) wyświetlacza.

void display_select_pos(uint8_t pos){

/* obsluga pozycji dziesietnych wyswietlacza (wspolne anody) */

switch(pos){

case 0:
GPIO_ResetBits(DISP_COM1_port, DISP_COM1_pin);
GPIO_SetBits(DISP_COM2_port, DISP_COM2_pin);
GPIO_SetBits(DISP_COM3_port, DISP_COM3_pin);
GPIO_SetBits(DISP_COM4_port, DISP_COM4_pin);
break;

case 1:
GPIO_SetBits(DISP_COM1_port, DISP_COM1_pin);
GPIO_ResetBits(DISP_COM2_port, DISP_COM2_pin);
GPIO_SetBits(DISP_COM3_port, DISP_COM3_pin);
GPIO_SetBits(DISP_COM4_port, DISP_COM4_pin);
break;

case 2:
GPIO_SetBits(DISP_COM1_port, DISP_COM1_pin);
GPIO_SetBits(DISP_COM2_port, DISP_COM2_pin);
GPIO_ResetBits(DISP_COM3_port, DISP_COM3_pin);
GPIO_SetBits(DISP_COM4_port, DISP_COM4_pin);
break;

case 3:
GPIO_SetBits(DISP_COM1_port, DISP_COM1_pin);
GPIO_SetBits(DISP_COM2_port, DISP_COM2_pin);
GPIO_SetBits(DISP_COM3_port, DISP_COM3_pin);
GPIO_ResetBits(DISP_COM4_port, DISP_COM4_pin);
break;

default:
GPIO_SetBits(DISP_COM1_port, DISP_COM1_pin);
GPIO_SetBits(DISP_COM2_port, DISP_COM2_pin);
GPIO_SetBits(DISP_COM3_port, DISP_COM3_pin);
GPIO_SetBits(DISP_COM4_port, DISP_COM4_pin);

}
}

void display_select_digit(uint8_t dig){

/* obsluga segmentow wyswietlacza (katody) */

switch(dig){

case 0:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_SetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_ResetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 1:
GPIO_ResetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_ResetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_ResetBits(DISP_F_port, DISP_F_pin);
GPIO_ResetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 2:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_ResetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_SetBits(DISP_E_port, DISP_E_pin);
GPIO_ResetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 3:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_ResetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 4:
GPIO_ResetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_ResetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 5:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_ResetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);

GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 6:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_ResetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_SetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 7:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_ResetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_ResetBits(DISP_F_port, DISP_F_pin);
GPIO_ResetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 8:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_SetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

case 9:
GPIO_SetBits(DISP_A_port, DISP_A_pin);
GPIO_SetBits(DISP_B_port, DISP_B_pin);
GPIO_SetBits(DISP_C_port, DISP_C_pin);
GPIO_SetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_SetBits(DISP_F_port, DISP_F_pin);
GPIO_SetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);
break;

default:
GPIO_ResetBits(DISP_A_port, DISP_A_pin);
GPIO_ResetBits(DISP_B_port, DISP_B_pin);
GPIO_ResetBits(DISP_C_port, DISP_C_pin);
GPIO_ResetBits(DISP_D_port, DISP_D_pin);
GPIO_ResetBits(DISP_E_port, DISP_E_pin);
GPIO_ResetBits(DISP_F_port, DISP_F_pin);
GPIO_ResetBits(DISP_G_port, DISP_G_pin);
GPIO_ResetBits(DISP_DP_port, DISP_DP_pin);

}

}

Listing 5.

Całość obsługi 4-cyfrowego wskaźnika LED sprowadza się do cyklicznego wywoływania dwóch funkcji (listing 5):

  • display_select_pos(pos), która jako parametr pos przyjmuje numer pozycji (0 – skrajna cyfra z lewej, 3 – skrajna cyfra z prawej),
  • display_select_digit(dig), która w roli parametru dig pobiera aktualną cyfrę (0...9) do wyświetlenia na danej pozycji.

Funkcje te są wywoływane w pętli nieskończonej, w ramach której program „rozkłada” aktualizowany okresowo numer do wyświetlenia (0000...9999) na cztery cyfry, odpowiadające kolejnym pozycjom dziesiętnym wyświetlacza. Całkowite wygaszenie wyświetlacza (niezbędne do prawidłowego odświeżenia jego zawartości) następuje przed ustawieniem cyfry i jest realizowane poprzez wywołanie funkcji display_select_pos() z parametrem 255 – w istocie można tu zastosować dowolną liczbę spoza „legalnego” zakresu 0...3, gdyż każdy wybór nienależący do tego przedziału zostanie i tak obsłużony przez sekcję default instrukcji warunkowej switch.

W celu urozmaicenia (a także zobrazowania sposobu obsługi linii wejściowych GPIO) dodano ponadto obsługę dwóch znajdujących się na płytce przycisków SW1 i SW2. Naciśnięcie pierwszego z nich tymczasowo blokuje odliczanie, ale nie wpływa na pracę systemu multipleksowania segmentów wyświetlacza.

Wciśnięcie SW2 powoduje natomiast przyspieszenie odliczania – zamiast domyślnej inkrementacji (cnt++) otrzymujemy bowiem możliwość „przeskakiwania” co 10 zliczeń. Zastosowana w tych dwóch przypadkach funkcja odczytu stanu linii I/O – GPIO_ReadInputDataBit() – raczej nie wymaga większych wyjaśnień. Dodajmy tylko dla porządku, że zwracana przez nią wartość uint8_t odpowiada rzeczywistemu stanowi logicznemu danego bitu (0 lub 1). Ciało głównej funkcji programu pokazano na listingu 6.

int main(void)
{

int16_t cnt = 0;
int16_t number = 0;
uint8_t pos = 0;

initPeripherals();

LED_onoff(1, LED_OFF);
LED_onoff(2, LED_OFF);

while (1)
{
/* odczyt stanu przycisku SW1 (1 = niewcisniety, 0 = wcisniety) */
/* Nacisniecie SW1 zatrzymuje odliczanie */
if(GPIO_ReadInputDataBit(SW1_port, SW1_pin))
cnt++;

if(cnt > 100){

cnt = 0;

/* Nacisniecie SW2 przyspiesza odliczanie */
if(GPIO_ReadInputDataBit(SW2_port, SW2_pin))
number++;
else
number += 10;

/* Obsluga przepelnienia licznika */
if(number > 9999) number = 0;

}

int8_t digits[4];

/* Pozyskanie kolejnych pozycji dziesietnych */
digits[0] = (number % 10000) / 1000;
digits[1] = (number % 1000) / 100;
digits[2] = (number % 100) / 10;
digits[3] = (number % 10);

/* Tymczasowe wylaczenie wyswietlacza */
display_select_pos(255);

/* Ustawienie cyfry do wyswietlenia na katodach */
display_select_digit(digits[pos]);

/* Wlaczenie wybranej pozycji */
display_select_pos(pos);

/* Obsluga licznika pozycji dziesietnych */
pos++;
if(pos > 3) pos = 0;

delay(1);

}

}

Listing 6.

Podsumowanie

W pierwszym odcinku naszego kursu nauczyliśmy się, jak zainstalować i skonfigurować środowisko Keil MDK do pracy z mikrokontrolerami Megawin z serii MG32F103. Napisaliśmy także dwa pierwsze programy, pozwalające na przetestowanie najprostszych spośród znajdujących się na naszej płytce ewaluacyjnej elementów sprzętowych: diod LED, wyświetlacza 7-segmentowego oraz przycisków. W kolejnych odcinkach pokażemy tajniki obsługi interfejsów I²C, SPI, UART, a także przetwornika ADC i wbudowanego zegara czasu rzeczywistego (RTC).

inż. Przemysław Musz, EP

Artykuł ukazał się w
Elektronika Praktyczna
maj 2024
DO POBRANIA
Materiały dodatkowe

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik październik 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio wrzesień - październik 2024

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje wrzesień 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna październik 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich październik 2024

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów