[3] Kurs: pierwsze kroki z STM32F0DISCOVERY. Zabawy z USART
Zliczanie czasu i transmisja USART z użyciem DMA
Kolejny program będzie transmitował dane przez USART korzystając z modułu bezpośredniego dostępu do pamięci, czyli bez udziału oprogramowania w transmisji każdego bajtu. Tam razem program będzie pełnił rolę prostego timera, odliczającego czas w sekundach. Naciśnięcie przycisku umieszczonego na płytce będzie powodowało wyzerowanie i restart timera. Co jedną sekundę bieżąca wartość timera będzie transmitowana przez USART – można ją wyświetlić na PC korzystając z programu terminala. Program, powstały przez modyfikację poprzedniego przykładu, jest zawarty w projekcie UART1dma.
Oprogramowanie modułu USART i DMA
Moduł USART1 jest inicjowany podobnie jak w poprzednim przykładzie. Wprowadzono jedną istotną zmianę – przed finalnym uaktywnieniem USART w rejestrze CR modułu jest ustawiony bit włączający zgłaszanie żądania DMA przez nadajnik.
Na początku sekwencji inicjującej jest włączany moduł DMA – wymaga to ustawienia odpowiedniego bitu w rejestrze AHBENR modułu RCC.
Sterownik bezpośredniego dostępu do pamięci zrealizowany w STM32F05x dysponuje pięcioma kanałami transmisji. Z nadajnikiem USART1 jest związany kanał 2; programując moduł DMA będziemy używali rejestrów tego kanału, dostępnych w postaci struktury poprzez wskaźnik DMA1_Channel2.
Do jednorazowego zainicjowania modułu DMA potrzebne są dwa zapisy rejestrów adresów. Ponieważ specyfikowanie adresów jako stałych typów numerycznych generuje ostrzeżenie w ANSI C, zapisy te zostały umieszczone w funkcji main. Pełne programowanie modułu DMA do współpracy z nadajnikiem USART1 wymaga łącznie czterech operacji:
- Ustawienia adresu początkowego bufora w pamięci, z którego będą pobierane dane, w rejestrze CMAR.
- Ustawienia adresu rejestru danych nadajnika USART w rejestrze CMAR.
- Ustawienia liczby transmitowanych bajtów w rejestrze CNDTR.
- Ustawienia trybu transmisji oraz uaktywnienia kanału DMA poprzez zapis do rejestru sterującego kanału CCR bitów: inkrementacji adresu pamięci – MINC, kierunku transmisji – z pamięci do modułu peryferyjnego – DIR oraz aktywacji kanału – EN.
Z powodu wymagań standardu ANSI języka C zapis rejestrów adresowych kanału DMA następuje w funkcji main – umieszczenie go w tablicy inicjowania peryferiali spowodowałoby wygenerowanie przez kompilator ostrzeżeń o użyciu stałych wbrew standardowi.
Moduł DMA po zakończeniu transmisji zachowuje początkowe wartości rejestrów adresowych, ale wymaga zapisu długości bloku danych przed każdą transmisją. Dodatkowo rejestr długości bloku może być zapisany tylko wówczas, gdy kanał jest nieaktywny, a kanał nie deaktywuje się samoczynnie po zakończeniu transmisji bloku. Z tych powodów w procedurze obsługi przrwania timera, w miejscu, gdzie następuje uaktywnienie kanału i rozpoczęcie transmisji znalazła się sekwencja trzech instrukcji:
- deaktywacji kanału – zapis 0 do CCR,
- zapisu długości bloku do CNDTR,
- powtórnej aktywacji kanału – zapis słowa sterującego do CCR.
Obsługa przerwania timera
Przerwanie timera generującego przebiegi PWM sterujące diodami jest zgłaszane tak samo, jak we wcześniejszych projektach, z częstotliwością 400 Hz. Jego obsługa obejmuje kilka czynności, wykonywanych z różnymi częstotliwościami:
- płynną modyfikację wypełnień PWM dla diod – 400 Hz;
- reakcję na przycisk zerowania timera – 100 Hz;
- zliczanie czasu w timerze programowym i błyskanie zieloną diodą – 1 Hz.
W celu uniknięcia konwersji wartości timera z postaci binarnej na reprezentację znakową, wartość ta jest przechowywana w postaci znakowej, w tablicy znakowej timer[]. Tablica ta, o długości 8 bajtów, zawiera na pozycjach 0..5 sześć cyfr timera, a na pozycjach 6 i 7 – sekwencję końca wiersza, przesyłaną wraz z wartością timera przez łącze szeregowe.
Blok reakcji na naciśnięcie przycisku jest wywoływany, tak jak poprzednio, w co czwartym przerwaniu. W bloku tym sprawdzany jest stan przycisku oraz inkrementowany jest licznik sdiv, zliczający setne części sekundy. Przy naciśnięciu przycisku zerowany jest timer odliczający sekundy oraz licznik setnych części sekundy. Dodatkowo jest zaświecana niebieska dioda; jej wygaszanie rozpoczyna się po upływie ? sekundy po naciśnięciu przycisku. Po zinkrementowaniu licznika setnych do wartości 100 jest on zerowany oraz następuje inkrementacja timera programowego. Inkrememtacja ta jest wykonywana w prostej pętli for(). Zarówno inkrementacja, jak i wyzerowanie licznika kończy się ustawieniem zmiennej logicznej upd_time, co w dalszej części przerwania timera powoduje zainicjowanie transmisji całego bufora timera programowego przez USART.
/* STM32F0DISCOVERY tutorial USART1 with DMA Tx + TIM3 timer &PWM blinker gbm, 02'2013 */ #include "stm32f0xx.h" //======================================================================== // STM32F05x #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; //======================================================================== // 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 BTN_ACK_PERIOD 25 // * 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[] = { // clock enable {&RCC->APB1ENR, RCC_APB1ENR_TIM3EN}, {&RCC->APB2ENR, RCC_APB2ENR_USART1EN}, { &RCC->AHBENR, RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_FLITFEN | RCC_AHBENR_SRAMEN | RCC_AHBENR_DMA1EN }, // port setup {&GPIOA->AFR[1], 1 << (2 <<2) | 1 <<(1 <<2)}, // USART pins 10 - RX, 9 - TX {&GPIOA->PUPDR, GPIO_PUPDR_PD <<(10 <<1)}, // pulldn on PA10 { &GPIOA->MODER, GPIOA_MODER_SWD | GPIO_MODER_AF <<(10 <<1) | GPIO_MODER_AF <<(9 <<1) }, // 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 // USART1 setup {(__IO32p)&USART1->BRR, (SYSCLK_FREQ + BAUD_RATE / 2) / BAUD_RATE}, // {&USART1->CR2, USART_CR2_TXINV | USART_CR2_RXINV}, // Invert TX &RX {&USART1->CR3, USART_CR3_DMAT}, // enable Tx DMA {&USART1->CR1, USART_CR1_TE | USART_CR1_RE | USART_CR1_UE}, // enable // DMA init moved to main() to avoid ANSI C warnings // PWM timer setup - 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} }; //======================================================================== static uint8_t time[8] = "000000\r\n"; //======================================================================== int main(void) { writeregs(init_table); DMA1_Channel2->CMAR = (uint32_t)time; DMA1_Channel2->CPAR = (uint32_t)&USART1->TDR; __WFI(); // go to sleep } //======================================================================== void TIM3_IRQHandler(void) { static uint8_t bstate = 0; static uint8_t blue_led_timer = 0; static uint8_t tdiv = 0; static uint8_t blue_target = LED_DIM, green_target = LED_DIM; static uint8_t sdiv = 0; uint32_t pwmval; TIM3->SR = ~TIM_SR_UIF; // clear interrupt flag if ((++ tdiv &3) == 0) { // 100 Hz int i; _Bool upd_time = 0; if ((bstate = (bstate << 1 &0xf) | (BUTTON_PORT->IDR >>BUTTON_BIT &1)) == 1) { // button pressed - clear timer for (i = 0; i <6; i ++) time[i] = '0'; sdiv = 0; upd_time = 1; blue_target = LED_MAX; blue_led_timer = BTN_ACK_PERIOD; } else if (blue_led_timer &&-- blue_led_timer == 0) blue_target = LED_DIM; if (++ sdiv == 100) { sdiv = 0; // increment time for (i = 5; i >= 0 &&++ time[i] >'9'; i --) time[i] = '0'; upd_time = 1; green_target = LED_MAX; } else if (sdiv == 50) green_target = LED_DIM; if (upd_time) { // init DMA for time string transfer DMA1_Channel2->CCR = 0; // disable DMA1_Channel2->CNDTR = sizeof(time); // no. of items // increment memory adress, mem->periph, enable DMA1_Channel2->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_EN; } } if ((pwmval = BLUE_LED_PWM) != blue_target) BLUE_LED_PWM = pwmval
Grzegorz Mazur
gbm@ii.pw.edu.pl