Obsługa interfejsu UART
Asynchroniczny interfejs szeregowy UART jest jednym z kilku najpopularniejszych standardów wymiany danych w systemach wbudowanych. Nic więc dziwnego, że także w strukturze naszego bohatera – mikrokontrolera MG32F103RBT6 – znajdziemy trzy takie bloki, przy czym w naszych eksperymentach posłużymy się peryferium o numerze 1. Do komunikacji posłużą nam dwie linie portu GPIOA:
PA9 – wyjście danych (TX),
PA10 – wejście danych (RX).
Aby przeprowadzić ćwiczenia opisane w niniejszym artykule, potrzebować będziemy dowolnego konwertera USB-UART pracującego z poziomami logicznymi 3,3 V. Konwerter podłączamy do złącza goldpin oznaczonego – a jakże – napisem UART, pamiętając o skrzyżowaniu linii danych (oznaczenia RX, TX znajdujące się na naszej płytce ewaluacyjnej odnoszą się do kierunków linii procesora).
Projekt 06 realizuje bodaj najprostszą funkcjonalność – „odbija” echo znaków odebranych z terminala komputera z powrotem do niego. Główny program jest zatem bardzo prosty (listing 1) – po wywołaniu funkcji konfigurującej blok UART oraz wyświetleniu komunikatu powitalnego procesor przechodzi do oczekiwania na znaki z użyciem funkcji blokującej UART_getchar(). Każdy odebrany bajt jest od razu przekazywany do funkcji UART_print(), która jako argument przyjmuje wskaźnik na zmienną typu char.
Projekt 06 – obsluga interfejsu UART */
#include "mg32f10x.h"
#include "hardware.h"
int main(void)
{
char c;
/* inicjalizacja pozostalych peryferiow */
initPeripherals();
/* konfiguracja UART-a: 9600 bps @ 72 MHz */
UART_config(72000000, 9600);
/* wyswietlenie komunikatu powitalnego */
UART_print("Kurs EP – programowanie MCU Megawin!\n");
UART_print("UART echo\n");
UART_print(">>");
while (1)
{
/* odbior znaku z terminala */
c = UART_getchar();
/* echo z powrotem na terminal */
UART_print(&c);
}
}
Listing 1. Kod programu głównego UART echo
Na listingu 2 pokazano ciała wszystkich trzech funkcji służących do obsługi UART-a. Dokładne komentarze dopisane do każdej linii wyjaśniają znaczenie poszczególnych funkcji bibliotecznych – zrozumienie kolejnych realizowanych operacji nie powinno zatem nastręczać większych problemów. Zwróćmy jednak uwagę na kilka mniej oczywistych aspektów.
/* zmienna do obliczenia rejestrow timingu */
uint32_t div;
/* wlaczenie taktowania interfejsu UART1 */
RCC->APB1PRE |= RCC_APB1PRE_SRCEN;
RCC->APB1ENR |= (RCC_APB1ENR_BMX1EN | RCC_APB1ENR_UART1EN);
/* reset interfejsu UART1 */
RCC->APB1RSTR |= RCC_APB1RSTR_UART1RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_UART1RST;
/* wyzerowanie rejestru funkcji modemowych */
UART1->MCR = 0x00;
/* obliczenie wartosci dzielnika */
div = apbclk_hz / baudrate_bps;
/* ulamkowa czesc dzielnika – tylko 4 najmlodsze bity */
UART1->DLF = div & 0x0F;
/* zezwolenie na zapis calkowitej czesci dzielnika */
UART1->LCR = UART_LCR_DLAB;
/* zapis calkowitej czesci dzielnika */
/* mlodsze polslowo */
UART1->DLL = (uint8_t)(div >> 4);
/* starsze polslowo */
UART1->DLH = (uint8_t)(div >> 12);
/* konfiguracja trybu transmisji: 8N1 */
UART1->LCR = UART_LCR_WLS_8BIT | UART_LCR_SBS_1BIT | UART_LCR_PARITY_NONE;
/* wlaczenie FIFO */
UART1->FCR = UART_FCR_FIFOE;
}
void UART_print(char *str)
{
/* petla po wszystkich znakach */
/* az do wystapienia znaku konca stringa = 0x00 */
while(*str)
{
/* oczekiwanie na zwolnienie bufora nadawczego */
while(!(UART1->LSR & UART_LSR_THRE)){};
/* zapis kolejnego bajtu do bufora */
UART1->THR = *str;
/* inkrementacja wskaznika */
str++;
}
/* oczekiwanie na wyslanie ostatniego bajtu */
while(!(UART1->LSR & UART_LSR_TEMT));
}
char UART_getchar(void){
/* oczekiwanie na odbior bajtu */
while(!(UART1->LSR & UART_LSR_DR));
/* zwrocenie odebranego bajtu */
return (UART1->RBR);
}
Listing 2. Funkcje do obsługi interfejsu UART
Dość mało intuicyjny może wydawać się sposób konfiguracji szybkości transmisji (baudrate). Nota katalogowa naszego mikrokontrolera podaje, że wartość dzielnika (obniżającego częstotliwość sygnału taktowania, czyli – w naszym przypadku – 72 MHz-owego przebiegu dostarczanego przez wewnętrzną pętlę PLL) można obliczyć poprzez podzielenie tejże częstotliwości przez 16-krotność docelowej prędkości transmisji (czyli 9600 bps). Ponieważ jednak dzielnik uwzględnia nie tylko część całkowitą, ale także 4-bitową część ułamkową, to w rzeczywistości wystarczy jedynie podzielić 72 MHz przez 9600 Hz, a ów wynik najpierw poddać funkcji iloczynu logicznego z maską 0x0F (ekstrakcja części ułamkowej), a następnie odpowiednio poprzesuwać w prawo – w celu wypełnienia zawartości młodszego i starszego rejestru przechowującego część całkowitą (DLL, DLH). Uważni Czytelnicy zauważą, że „w międzyczasie” musimy także odblokować dostęp do rejestrów dzielnika ustawiając bit UART_LCR_DLAB, co zdaniem producenta ma... wprowadzić dodatkowe zabezpieczenie.
W przypadku funkcji UART_print() warto zwrócić uwagę, że pętla while() automatycznie kończy swoje działanie po napotkaniu znaku końca stringa, czyli bajtu o wartości 0x00. Należy o tym pamiętać wykonując manipulacje na tablicach typu char[]. W przypadku używania funkcji UART_print() z „natywnymi” ciągami znaków (ujętymi w cudzysłowy), kompilator wprowadzi znak końca za nas, dzięki czemu nie musimy się martwić o niekontrolowane zapętlenie programu.
Obsługa zegara czasu rzeczywistego
Kolejny program – Projekt 07 – pozwala zapoznać się z metodami obsługi wbudowanego zegara RTC naszego mikrokontrolera. Przed przystąpieniem do eksperymentów należy upewnić się, że w gnieździe na płytce ewaluacyjnej znajduje się sprawna bateria CR2032, zaś jumper VBATSET jest ustawiony w pozycji BAT.
uint8_t str_idx = 0;
uint32_t hh = 0, mm = 0, ss = 0;
int main(void)
{
initPeripherals();
UART_config(72000000, 9600);
UART_print("Kurs EP – programowanie MCU Megawin!\n");
UART_print("RTC test\n");
/* wlaczenie taktowania domeny BACKUP */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_BKP, ENABLE);
/* odblokowanie dostepu do domeny BACKUP */
PWR_BackupAccessCmd(ENABLE);
/* konfiguracja NVIC-a */
NVIC_Config();
/* sprawdzenie wartosci kontrolnej */
/* jezeli rejestr DR1 = 0x1234 przechodzimy do normalnej pracy */
/* jezeli nie, to inicjalizujemy RTC i rejestr DR1 */
if (BKP_ReadBackupRegister(BKP_DR1) != 0x1234)
{
/* wstepna konfiguracja RTC */
RTC_Config();
/* zapisanie wartosci kontrolnej */
BKP_WriteBackupRegister(BKP_DR1, 0x1234);
}
else
{
/* oczekiwanie na synchronizacje z szyna systemowa */
RTC_WaitForSynchro();
/* wlaczenie obslugi przerwania od sekund */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* oczekiwanie na wykonanie ostatniej operacji */
RTC_WaitForLastTask();
/* ustawienie preskalera: 32,768 kHz/(32767+1) = 1 Hz */
RTC_SetPrescaler(32767);
/* oczekiwanie na wykonanie ostatniej operacji */
RTC_WaitForLastTask();
}
while (1)
{
/* sprawdzenie flagi aktualizacji wyswietlacza (co 1 sekunde) */
if(disp_time_flag){
/* wlaczenie LED 1 */
LED_onoff(1, LED_ON);
/* zerowanie flagi */
disp_time_flag = 0;
/* reset licznika RTC po osiagnieciu pelnej doby (23:59:59) */
if (RTC_GetCounter() >= 0x0001517F)
{
/* zerowanie licznika RTC */
RTC_SetCounter(0x0);
/* oczekiwanie na wykonanie ostatniej operacji */
RTC_WaitForLastTask();
}
/* odczyt stanu licznika RTC */
uint32_t timestmp = RTC_GetCounter();
/* obliczenie godzin, minut i sekund */
hh = timestmp / 3600;
mm = (timestmp % 3600) / 60;
ss = (timestmp % 3600) % 60;
/* "konkatenacja" godzin i minut dla wyswietlacza LED */
disp_num = hh*100 + mm;
delay(10);
/* wylaczenie diody LED 1 */
LED_onoff(1, LED_OFF);
}else{
/* obsluga terminala jezeli nie trzeba aktualizowac wysw. LED */
/* sprawdzenie czy odebrano znak z terminala */
if(UART1->LSR & UART_LSR_DR){
/* zapis znaku do stringa */
str[str_idx] = UART1->RBR;
/* inkrementacja indeksu stringa */
str_idx++;
if(str_idx > 7){
str_idx = 0;
/* pobranie wartosci z ciagu HH:MM:SS */
sscanf(str, "%d:%d:%d", &hh, &mm, &ss);
if((hh >=0) && (hh <=23)){
if((mm >=0) && (mm <=59)){
if((ss >=0) && (ss <=59)){
/* przeliczenie nowej godziny na sekundy i aktualizacja licznika */
RTC_SetCounter((uint32_t)hh*3600 + mm*60 + ss);
}
}
}
}
}
}
}
}
Listing 3. Fragment pliku main.c do przykładu 7
Nasz program (listing 3) rozpoczyna działanie od konfiguracji niezbędnych peryferiów, po czym od razu przechodzi do sprawdzenia, czy zawartość rejestru DR1 (znajdującego się w domenie BACKUP, czyli części procesora podtrzymywanej napięciem z wejścia VBAT) jest zgodna z arbitralnie przyjętym „kluczem” (w naszym przykładzie jest to wartość 0x1234). Jeżeli nie – a taka sytuacja ma miejsce po pierwszym uruchomieniu programu lub po zaniku obydwu napięć zasilania (głównego oraz zapasowego) – to program przechodzi do wstępnej konfiguracji RTC, w ramach której wykonuje także inicjalizację licznika przez wpisanie godziny 12:00:00 (uwaga – licznik przechowuje liczbę sekund od północy). Jeśli zegar został już wcześniej zainicjalizowany, to mikrokontroler wykonuje kilka podstawowych operacji (włączenie obsługi przerwań 1-sekundowych i ustawienie preskalera), po czym wchodzi do pętli głównej. Każda iteracja rozpoczyna się od sprawdzenia, czy procedura obsługi przerwania od RTC ustawiła flagę disp_time_flag – jeżeli tak, program pobiera aktualny czas, przelicza go na godziny, minuty i sekundy, po czym wyświetla go na wyświetlaczu LED. Nasz wskaźnik ma tylko cztery cyfry, dlatego zamiast pokazywania liczby sekund stosujemy inny, bardzo prosty trik – sygnalizujemy upływ kolejnych sekund za pomocą krótkich błysków diody LED D1. W czasie pomiędzy aktualizacjami wyświetlacza program realizuje odbiór znaków z terminala – jeżeli użytkownik wpisze z klawiatury nową godzinę w formacie HH:MM:SS i naciśnie klawisz Enter, zawartość bufora odbiorczego zostanie przekazana do funkcji sscanf(), zaś odpowiednie instrukcje warunkowe zwalidują wykryte liczby. Dzięki temu zegar zostanie zaktualizowany wyłącznie wtedy, gdy wszystkie trzy części (godziny, minuty i sekundy) będą się mieścić w poprawnych zakresach.
{
/* reset domeny BACKUP */
BKP_DeInit();
/* wlaczenie oscylatora LSE (32,768 kHz) */
BKP_LSEConfig(BKP_LSE_ON);
/* oczekiwanie na uruchomienie LSE */
while (BKP_GetLSEReadyFlagStatus() == RESET){}
/* wybor LSE jako zrodla taktowania dla RTC */
BKP_RTCCLKConfig(BKP_RTCCLKSource_LSE);
/* wlaczenie RTC */
BKP_RTCCLKCmd(ENABLE);
/* oczekiwanie na synchronizacje z szyna systemowa */
RTC_WaitForSynchro();
/* oczekiwanie na zakonczenie ostatniej operacji */
RTC_WaitForLastTask();
/* wlaczenie przerwania od sekund */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* oczekiwanie na zakonczenie ostatniej operacji */
RTC_WaitForLastTask();
/* ustawienie preskalera: 32,768 kHz/(32767+1) = 1 Hz */
RTC_SetPrescaler(32767);
/* oczekiwanie na zakonczenie ostatniej operacji */
RTC_WaitForLastTask();
/* ustawienie domyslnej godziny: 12:00:00 */
RTC_SetCounter(43200);
}
void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* konfiguracja przerwania od RTC*/
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
Listing 4. Funkcje do konfiguracji RTC i NVIC
Funkcje odpowiedzialne za inicjalizację RTC oraz kontrolera przerwań NVIC pokazano na listingu 4, zaś ciało procedury obsługi przerwania od RTC zamieszczono na listingu 5 – podobnie jak poprzednio, także w tym przypadku zamieszczone komentarze powinny całkowicie rozjaśnić sens zaprezentowanego kodu.
{
extern volatile uint8_t disp_time_flag;
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* Clear the RTC Second interrupt */
RTC_ClearITPendingBit(RTC_IT_SEC);
/* Enable time update */
disp_time_flag = 1;
}
}
Listing 5. Procedura obsługi przerwania od RTC
Obsługa interfejsu SPI – pamięć EEPROM
Na koniec naszego opracowania pozostawiliśmy zagadnienie, którego nie może zabraknąć w żadnym kursie programowania mikrokontrolerów: opiszemy bowiem działanie interfejsu SPI na przykładzie dwukierunkowej komunikacji (tryb full duplex) z pamięcią EEPROM znajdującą się na płytce ewaluacyjnej.
Z uwagi na fakt, że kod programu do Projektu 08 jest dość obszerny, w artykule przywołujemy jedynie jego najbardziej reprezentatywne części (kompletne kody źródłowe wraz z całą strukturą katalogów projektów opisanych w kursie znajdują się – jak zawsze – w materiałach dodatkowych umieszczonych na serwerze „Elektroniki Praktycznej”).
Zasada działania programu jest dość prosta – spójrzmy na listing 6. Na początku dokonujemy odczytu zawartości pierwszej strony pamięci zawierającej 64 bajty, począwszy od komórki o adresie 0x0000. Następnie wyświetlamy odebrane znaki (zakładając, że odczytana strona zawiera już jakiś czytelny tekst w formacie ASCII – rzecz jasna, za pierwszym razem w terminalu możemy zobaczyć „śmietnik” z uwagi na brak wcześniejszej inicjalizacji EEPROM-u). Po tej operacji wyświetlamy komunikat z prośbą o podanie nowego tekstu oraz znak zachęty („>>”). Nasłuch kończy się po odebraniu 64 znaków lub wciśnięciu klawisza Enter (\n). Kolejnym etapem działania programu jest odblokowanie pamięci w celu jej zapisu (wysłanie komendy 0x06, czyli WREN – Write Enable). Teraz możemy już dokonać właściwego zapisu nowego tekstu użytkownika, po czym wyświetlamy komunikat o powodzeniu operacji.
char tx_buf[67];
char rx_buf[67];
/* indeksy (wskazniki pozycji w buforach) */
uint8_t tx_index = 0;
uint8_t rx_index = 0;
/* liczba bajtow do wyslania */
uint8_t tx_len = 0;
/* pomocniczy string do obslugi UART-a */
char str [67];
int main(void)
{
/* (...) */
/* odczyt strony pamieci EEPROM (64 bajty) */
/* komenda READ */
tx_buf[0] = 0x03;
/* 16-bitowy adres – poczatek pamieci*/
tx_buf[1] = 0x00;
tx_buf[2] = 0x00;
/* 1 bajt komendy + 2 bajty adresu + 64 bajty pamieci */
tx_len = 67;
/* zerowanie indeksu bufora odbiorczego */
rx_index = 0;
/* wlaczenie przerwan */
SPI_ITConfig(SPIM2, SPI_IT_RXF | SPI_IT_TXE, ENABLE);
/* uruchomienie SPI */
SPI_Cmd(SPIM2, ENABLE);
/* oczekiwanie na zakonczenie odczytu */
delay(5);
/* dezaktywacja SPI */
SPI_Cmd(SPIM2, DISABLE);
/* zabezpieczenie przed wykroczeniem poza tablice */
/* w czasie "drukowania" znakow funkcja UART_print() */
rx_buf[66] = 0x00;
/* kopia bufora z pominieciem pierwszych 3 znakow */
for(uint8_t i = 3; i < 67; i++){
str[i-3] = rx_buf[i];
}
/* wyslanie tekstu na UART */
UART_print(str);
UART_print("\nWpisz nowy tekst: >> ");
/* odbieranie maks. 64 znakow */
for(uint8_t i = 3; i < 67; i++){
tx_buf[i] = UART_getchar();
/* przerwanie petli jezeli wcisnieto enter */
if(tx_buf[i] == ‘\n’) break;
}
/* wyslanie komendy odblokowania zapisu EEPROM-u */
tx_buf[0] = 0x06; // WREN
/* wystarczy jeden bajt */
tx_len = 1;
/* wlaczenie przerwan i wyslanie komendy */
SPI_ITConfig(SPIM2, SPI_IT_TXE, ENABLE);
SPI_Cmd(SPIM2, ENABLE);
delay(1);
SPI_Cmd(SPIM2,DISABLE);
/* komenda zapisu EEPROM-u */
tx_buf[0] = 0x02;
/* ten sam adres z ktorego byl odczyt */
tx_buf[1] = 0x00;
tx_buf[2] = 0x00;
tx_len = 67;
/* wlaczenie przerwan i wyslanie tekstu */
SPI_ITConfig(SPIM2, SPI_IT_TXE, ENABLE);
SPI_Cmd(SPIM2, ENABLE);
/* na wszelki wypadek odczekujemy dluzej niz trzeba */
delay(10);
SPI_Cmd(SPIM2, DISABLE);
UART_print("\nZapis zakonczony pomyslnie! \n");
UART_print("Zapisany tekst: ");
/* ponownie dokonujemy odczytu nowego tekstu z EEPROM-u */
tx_buf[0] = 0x03;
tx_buf[1] = 0x00;
tx_buf[2] = 0x00;
tx_len = 67;
rx_index = 0;
SPI_ITConfig(SPIM2, SPI_IT_RXF | SPI_IT_TXE, ENABLE);
SPI_Cmd(SPIM2, ENABLE);
delay(5);
SPI_Cmd(SPIM2, DISABLE);
/* wyslanie nowego tekstu na UART */
rx_buf[66] = 0x00;
for(uint8_t i = 3; i < 67; i++){
str[i-3] = rx_buf[i];
}
UART_print(str);
UART_print("\n");
while (1)
{}
}
Listing 6. Fragment pliku main.c do przykładu 8
Kompletny program realizuje także ponowny odczyt pamięci (w identyczny sposób, jak na początku) w celu wyświetlenia zapisanego chwilę wcześniej ciągu znaków – pozwala to natychmiast zorientować się, czy operacja przebiegła poprawnie.
SPI_InitTypeDef SPI_InitStructure;
/* wlaczenie taktowania SPI */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_BMX2
|RCC_APB2Periph_SPIM2, ENABLE);
/* reset ustawien SPI */
SPI_DeInit(SPIM2);
/* tryb full duplex */
SPI_InitStructure.SPI_TransferMode = SPI_TransferMode_TxAndRx;
/* rozmiar slowa */
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
/* polaryzacja i faza zegara */
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
/* preskaler (72 MHz / 72 = 1 MHz) */
SPI_InitStructure.SPI_BaudRatePrescaler = 72;
/* format ramki */
SPI_InitStructure.SPI_FrameFormat = SPI_FrameFormat_SPI;
/* inicjalizacja bloku SPI */
SPI_Init(SPIM2, &SPI_InitStructure);
/* wylaczenie przerwan */
SPI_ITConfig(SPIM2, 0xFF, DISABLE);
/* ustawienie progow wyzwalania przerwan od FIFO */
SPI_TxFIFOThresholdConfig(SPIM2, 2);
SPI_RxFIFOThresholdConfig(SPIM2, 1);
/* wybor wyjscia CS (sterowanie sprzetowe) */
SPI_NSSConfig(SPIM2, SPI_NSS_0, ENABLE);
}
Listing 7. Funkcja konfigurująca SPI
Prosta i całkiem intuicyjna funkcja konfiguracji interfejsu SPI została pokazana na listingu 7, zaś fragment procedury obsługi przerwań od SPI znajduje się na listingu 8. W tym miejscu warto zwrócić uwagę na sposób realizacji transmisji danych – używamy do tego celu przerwań, które blok SPI generuje tuż przed zapełnieniem sprzętowego, odbiorczego bufora FIFO, a także tuż przed opróżnieniem bufora nadawczego. Dane są zapisywane i odczytywane (już na drodze programowej) do/z odpowiednich buforów utworzonych jako globalne tablice typu char tx_buf[67] oraz rx_buf[67], przy czym liczba transferów jest dynamicznie zmieniana z poziomu programu głównego na drodze modyfikacji zawartości zmiennej tx_len.
{
extern uint8_t tx_buf[67];
extern uint8_t rx_buf[67];
extern uint8_t tx_index;
extern uint8_t rx_index;
extern uint8_t tx_len;
if(SPI_GetITStatus(SPIM2, SPI_IT_RXF) != RESET)
{
while(SPI_GetFlagStatus(SPIM2, SPI_FLAG_RFNE) != RESET)
{
rx_buf[rx_index] = SPI_ReadData(SPIM2);
rx_index++;
if(rx_index >= tx_len) {
SPI_ITConfig(SPIM2, SPI_IT_RXF, DISABLE);
break;
}
}
}
/* jezeli bufor nadawczy jest pusty */
if(SPI_GetITStatus(SPIM2, SPI_IT_TXE) != RESET)
{
/* powtarzanie dopoki bufor sie nie zapelni */
while(SPI_GetFlagStatus(SPIM2, SPI_FLAG_TFNF) != RESET)
{
/* wysylka kolejnego bajtu z bufora */
SPI_WriteData(SPIM2, tx_buf[tx_index]);
/* inkrementacja indeksu bufora */
tx_index++;
/* konczenie wysylki i dezaktywacja przerwania od TX */
if(tx_index >= tx_len) {
tx_index = 0;
SPI_ITConfig(SPIM2, SPI_IT_TXE, DISABLE);
break;
}
}
}
}
Listing 8. Procedura obsługi przerwań od SPI
Podsumowanie
W ramach naszego kursu opisaliśmy w telegraficznym skrócie obsługę najważniejszych bloków peryferyjnych mikrokontrolera Megawin MG32F103RBT6. Celem zaprezentowanego materiału było zaznajomienie Czytelników z tymi interesującymi, budżetowymi procesorami, które z powodzeniem mogą zastąpić droższe i popularniejsze układy STM32F1. Warto zwrócić uwagę, że zaprezentowaliśmy zaledwie część możliwych metod konfiguracji i użytkowania peryferiów sprzętowych – mikrokontrolery MG32F dysponują wszak rozbudowanym kontrolerem DMA, pozwalającym na zautomatyzowanie większości transferów danych pomiędzy poszczególnymi blokami sprzętowymi a pamięcią. Układy te mają także wbudowany interfejs USB, otwierający zupełnie nowe możliwości aplikacyjne. Czytelników zainteresowanych zgłębianiem dalszych tajników procesora MG32F103RBT6 (oraz innych modeli z tej rodziny) zachęcamy gorąco do studiowania przykładów dostarczonych przez producenta. Niektóre z nich (np. dotyczące obsługi USB w trybie wirtualnego portu szeregowego) można z niewielkimi modyfikacjami uruchomić na płytce ewaluacyjnej, inne będą wymagały dostosowania większej części programu – pomocna okaże się także dokumentacja dostarczana w „paczce” razem z przykładami i plikami bibliotecznymi.
Mamy nadzieję, że zaprezentowany materiał – jako pierwszy (nie tylko w Polsce, ale i na świecie) kurs programowania mikrokontrolerów Megawin – zachęci naszych Czytelników zarówno do samodzielnych eksperymentów, jak i wdrożenia tych niedrogich, wydajnych procesorów we własnej praktyce projektowej.
Powodzenia!
inż. Przemysław Musz, EP