[3] Kurs: pierwsze kroki z STM32F0DISCOVERY. Zabawy z USART
Uwaga! Opublikowane odcinki kurs są dostępne pod adresem.
Samouczek jest dedykowany szczególnie tym projektantom, którzy stają przed perspektywą zmiany mikrokontrolera z 8-bitowego na nowszy i tańszy 32-bitowy. Wszystkie programy są napisane w języku C. W przykładach nie użyto bibliotek służących do obsługi peryferiali, dzięki czemu kod programów jest krótki i czytelny, a zajętość pamięci znacznie mniejsza, niż w typowych programach demonstracyjnych, udostępnianych przez producentów mikrokontrolerów. Zwrócono również szczególną uwagę na poprawność prezentowanych rozwiązań i sposób zapisu programu ułatwiający optymalizację kodu i wychwytywanie błędów przez kompilator. Przedstawione programy zostały napisane w taki sposób, że nie generują one żadnych ostrzeżeń kompilatora. Przykłady przygotowano na zestaw STM32F0DISCOVERY.
Port szeregowy USART
Mikrokontrolery STM32F051 są wyposażone w sterownik transmisji szeregowej USART, który może służyć do transmisji zarówno asynchronicznej, jak i synchronicznej. Użyty w trybie asynchronicznym, może on np. sterować interfejsem RS232. Na płytce DISCOVERY (STM32F0DISCOVERY) nie ma translatorów poziomów RS232, jednak własności elektryczne i logiczne STM32F05x umożliwiają współpracę z interfejsem RS232 bez użycia translatora, o ile transmisja odbywa się w warunkach laboratoryjnych – na małą odległość i bez zakłóceń. Do połączenia mikrokontrolera z interfejsem RS232C komputera PC potrzebny będzie tylko jeden rezystor o wartości od 47 do 100 k?.
Przedstawiony poniżej projekt oprogramowania powstał przez modyfikację projektu BtnBlink. Nosi on nazwę USART1.
Zaprogramowanie modułu USART
Przed zaprogramowaniem modułu USART należy określić parametry transmisji. Wybierzemy szybkość 9600 bit/s, format 8-bitowy bez parzystości, z jednym bitem stopu. Wartość, która posłuży do zaprogramowania szybkości transmisji, zdefiniujemy jako wyrażenie preprocesora. Ponieważ nie użyjemy układu translatora poziomów RS232C, zaprogramujemy odwracanie poziomów napięć na liniach RX i TX.
W celu zaprogramowania modułu USART należy:
- Włączyć moduł poprzez ustawienie bitu USART1EN w rejestrze APB2ENR.
- Włączyć port GPIOA.
- Wybrać funkcję AF 1 (RX i TX USART1) dla wyprowadzeń 10 i 9 portu GPIOA.
- Włączyć ściąganie w dół dla linii RX
- Ustawić linie 10 i 9 portu GPIOA jako AF, pozostawiając funkcje interfejsu SWD dla linii 14 i 13.
- Ustawić podzielnik szybkości transmisji w rejestrze BRR modułu USART1.
- Włączyć odwracanie polaryzacji linii RX i TX – bity TXINV i RXINV rejestru CR2.
- Włączyć przerwanie odbioru danych, zezwolenie na nadawanie i odbiór oraz cały układ USART1 w rejestrze CR1 – musi to być ostatnia czynność podczas programowania rejestrów modułu USART.
- Włączyć przerwanie USART1 w module NVIC – rejestr ISER[0].
Ponieważ w projekcie użyjemy przerwania modułu USART, należy je oprogramować. Jedynym włączonym źródłem tego przerwania będzie odebranie danej. Procedura obsługi przerwania USART1 odczytuje odebrany znak i poddaje go prostemu przetwarzaniu. Małe litery są zamieniane na wielkie i odwrotnie, a odebranie kodu CR powoduje, poprzez ustawienie zmiennej mchange, zmianę trybu świecenia diod. Po przetworzeniu znak zostaje odesłany z powrotem.
Podczas wysyłania znaku do PC nie jest sprawdzana gotowość nadajnika USART. W naszym przykładzie nie ma to znaczenia, gdyż przy nadawaniu z komputera PC znaków wprowadzanych przez użytkownika w programie terminala nie ma możliwości przepełnienia bufora nadajnika interfejsu USART mikrokontrolera znakami odsyłanymi zwrotnie Nie jest to jednak uniwersalny sposób programowej obsługi nadawania, który może być zastosowany w każdej sytuacji. Poprawne nadawanie danych z użyciem przerwań zostanie zaprezentowane w następnym przykładzie.
Połączenie modułu STM32F0DISCOVERY z komputerem
W celu umożliwienia komunikacji modułu z komputerem PC należy poprowadzić trzy połączenia – masy oraz dwóch linii danych. Ponieważ zasilamy moduł STM32F0 z komputera, masa jest już połączona przez port USB. Do nawiązania transmisji wystarczy więc wykonać połączenia linii danych z komputera do modułu i z modułu do komputera. Wyjście danych mikrokontrolera – linię 9 portu GPIOA – łączymy bezpośrednio z wejściem danych komputera PC – linią 2 złącza DB9. Wejście danych mikrokontrolera – linię 10 portu GPIOA – łączymy przez rezystor szeregowy 100 k? z wyjściem danych komputera PC – linią 3 złącza DB9. Podczas wykonywania połączeń należy zachować szczególną uwagę – błędne podłączenie wyjścia danych mikrokontrolera do niewłaściwej linii złącza DB9 może spowodować trwałe uszkodzenie mikrokontrolera.
/* STM32F0DISCOVERY tutorial USART1 + TIM3 PWM blinker with table-driven init and blink mode switching gbm, 12'2012 */ #include "stm32f0xx.h" //======================================================================== // defs for STM32F05x chips #define GPIO_MODER_OUT 1 #define GPIO_MODER_AF 2 #define GPIOA_MODER_SWD (GPIO_MODER_AF <<(14 <<1) | GPIO_MODER_AF <<(13 <<1)) // keep SWD pins #define GPIO_PUPDR_PU 1 #define GPIO_PUPDR_PD 2 #define TIM_CCMR2_OC3M_PWM1 0x0060 // OC3M[2:0] - PWM mode 1 #define TIM_CCMR2_OC4M_PWM1 0x6000 // OC4M[2:0] - PWM mode 1 typedef __IO uint32_t * __IO32p; //======================================================================== // defs for STM32F0DISCOVERY board #define LED_PORT GPIOC #define BLUE_LED_BIT 8 #define GREEN_LED_BIT 9 #define BUTTON_PORT GPIOA #define BUTTON_BIT 0 #define BLUE_LED_PWM TIM3->CCR3 #define GREEN_LED_PWM TIM3->CCR4 //======================================================================== #define SYSCLK_FREQ HSI_VALUE #define BAUD_RATE 9600 #define BLINK_PERIOD 50 // * 10 ms #define BTN_ACK_PERIOD 50 // * 10 ms // PWM constants #define PWM_FREQ 400 // Hz #define PWM_STEPS 80 #define PWM_CLK SYSCLK_FREQ #define PWM_PRE (PWM_CLK / PWM_FREQ / PWM_STEPS) #define LED_MAX (PWM_STEPS - 1) #define LED_DIM 1 #define LED_OFF 0 //======================================================================== struct init_entry_ { volatile uint32_t *loc; uint32_t value; }; static __INLINE void writeregs(const struct init_entry_ *p) { for (; p->loc; p ++) *p->loc = p->value; } //======================================================================== void SystemInit(void) { FLASH->ACR = FLASH_ACR_PRFTBE; // enable prefetch } //======================================================================== static const struct init_entry_ init_table[] = { {&RCC->APB2ENR, RCC_APB2ENR_USART1EN}, // USART1 clock enable // port setup {&RCC->AHBENR, RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIOAEN}, // GPIOC, GPIOA {&GPIOA->AFR[1], 1 <<(2 <<2) | 1 <<(1 <<2)}, // set USART pins 10 - RX, 9 - TX {&GPIOA->PUPDR, GPIO_PUPDR_PD <<(10 << 1)}, // set pulldn on PA10 { &GPIOA->MODER, GPIOA_MODER_SWD | GPIO_MODER_AF <<(10 <<1) | GPIO_MODER_AF <<(9 <<1) }, // set UART pins as AF { &LED_PORT->MODER, GPIO_MODER_AF <<(GREEN_LED_BIT <<1) | GPIO_MODER_AF <<(BLUE_LED_BIT <<1) }, // set LED pins as AF {&RCC->AHBENR, RCC_AHBENR_GPIOAEN}, // GPIOA // USART1 setup {(__IO32p)&USART1->BRR, (SYSCLK_FREQ + BAUD_RATE / 2) / BAUD_RATE}, // {&USART1->CR2, USART_CR2_TXINV | USART_CR2_RXINV}, // Invert ext. lines {&USART1->CR1, USART_CR1_RXNEIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE}, // enable // PWM timer setup - TIM3 {&RCC->APB1ENR, RCC_APB1ENR_TIM3EN}, // TIM3 {(__IO32p)&TIM3->PSC, PWM_PRE - 1}, // prescaler {(__IO32p)&TIM3->ARR, PWM_STEPS - 1}, // period // blue - CH3, green - CH4 {(__IO32p)&TIM3->CCMR2, TIM_CCMR2_OC4M_PWM1 | TIM_CCMR2_OC4PE | TIM_CCMR2_OC3M_PWM1 | TIM_CCMR2_OC3PE}, // PWM mode 1, buffered preload {(__IO32p)&TIM3->CCER, TIM_CCER_CC4E | TIM_CCER_CC3E}, // enable CH3, 4 output {(__IO32p)&TIM3->DIER, TIM_DIER_UIE}, // enable update interrupt {(__IO32p)&TIM3->CR1, TIM_CR1_ARPE | TIM_CR1_CEN}, // auto reload, enable // interrupts and sleep {&NVIC->ISER[0], 1 <SCR, SCB_SCR_SLEEPONEXIT_Msk}, // sleep while not in handler {0, 0} }; //======================================================================== int main(void) { writeregs(init_table); __WFI(); // go to sleep } //======================================================================== static _Bool mchange = 0; //======================================================================== void TIM3_IRQHandler(void) { static uint8_t blink_timer = BLINK_PERIOD; static enum {BM_SLOW, BM_FAST, BM_OFF} blink_mode = BM_SLOW; static uint8_t bstate = 0; static uint8_t blue_led_timer = 0, on_time = 0; static const uint8_t blink_periods[] = {100, 50, 1}; static uint8_t tdiv = 0; static uint8_t blue_target = LED_DIM, green_target = LED_DIM; uint32_t pwmval; TIM3->SR = ~TIM_SR_UIF; // clear interrupt flag if ((++ tdiv &3) == 0) { bstate = (bstate <<1 & 0xf) | (BUTTON_PORT->IDR >>BUTTON_BIT &1); if (blue_led_timer) { if (-- blue_led_timer == 0) blue_target = LED_DIM; } else if (bstate == 1 || mchange) { // button pressed or CR received - change blink mode if (++ blink_mode >BM_OFF) blink_mode = BM_SLOW; blue_led_timer = BTN_ACK_PERIOD; blue_target = LED_MAX; USART1->TDR = blink_mode + '0'; mchange = 0; } if (-- blink_timer == 0) { blink_timer = blink_periods[blink_mode]; on_time = blink_timer >> 1; green_target = LED_DIM; } else if (blink_timer == on_time) green_target = LED_MAX; } if ((pwmval = BLUE_LED_PWM) != blue_target) BLUE_LED_PWM = pwmval ISR &USART_ISR_RXNE) // data received { uint8_t c; c = USART1->RDR; if (c == '\r') mchange = 1; else if ((c >= 'A' &&c <= 'Z') || (c >= 'a' &&c <= 'z')) c ^= 'A' ^ 'a'; USART1->TDR = c; } } //========================================================================