[3] Kurs: pierwsze kroki z STM32F0DISCOVERY. Zabawy z USART
Obsługa nadajnika USART z użyciem przerwań
Poprzedni przykład pokazuje dość prostą i nie nadającą się do szerszego zastosowania metodę nadawania danych przez moduł USART. W kolejnym projekcie zaprezentujemy nadawanie ciągów znaków przy użyciu przerwania zgłaszanego przy gotowości nadajnika. W każdym projekcie urządzenia nadającego dane musimy zadbać o to, by strumień danych nie przekraczał możliwości nadajnika. Nasz program będzie zliczał naciśnięcia przycisku. Po każdym naciśnięciu wartość licznika będzie wysyłana do PC przez interfejs UART w postaci 8-cyfrowej liczby szesnastkowej zakończonej sekwencją przejścia do nowego wiersza CR+LF. Każde naciśnięcie przycisku będzie więc powodowało transmisję 10 bajtów. Przesyłane dane można będzie oglądać przy użyciu programu terminala. Ponieważ z zastosowanego wcześniej algorytmu obsługi przycisku wynika, że można zarejestrowań maksymalnie 25 naciśnięć w ciągu sekundy, przy szybkości transmisji 9600 bit/s nie zachodzi obawa nasycenia nadajnika. Aby obsługa transmisji była uniwersalna, zastosujemy dla transmitowanych danych bufor cykliczny, pomimo że nie jest on niezbędny w naszym zastosowaniu, w którym długość przesyłanego bloku danych jest stała. Program powstanie przez modyfikację poprzedniego przykładu użycia USART. Gotowy projekt nosi nazwę USART1cnt.
Procedury obsługi nadawania
Zaczynamy od sekwencji inicjującej USART1. Tym razem nie będziemy używali przerwania odbiornika. Musimy za to oprogramować nadawanie danych przy użyciu przerwania nadajnika.
W programie zdefiniujemy bufor nadawanych znaków u1_outbuf[] oraz dwa indeksy – wskaźniki znaków wstawianych i pobieranych z bufora – u1_obiptr i u1_oboptr. Gdy bufor jest pusty, oba wskaźniki są równe. Przerwanie nadajnika będzie włączane tylko wtedy, kiedy w buforze znajdą się dane do nadawania.
Procedura obsługi przerwania USART1 jest wywoływana, gdy interfejs jest gotowy na przyjęcie danej do nadawania i włączone jest przerwanie nadajnika. Procedura najpierw oblicza przyszłą wartość wskaźnika danych pobieranych. Jeżeli po wysłaniu kolejnego znaku bufor zostałby opróżniony, przerwanie nadajnika jest wyłączane. Następnie znak z bufora jest ładowany do rejestru nadajnika oraz modyfikowany jest wskaźnik pobieranych danych.
Procedura wstawienia znaku do bufora USART1_put sprawdza, czy w buforze jest miejsce, a następnie wstawia znak do bufora, przesuwa wskaźnik wstawiania i włącza przerwanie nadajnika. W przypadku zapełnienia bufora procedura zwraca wartość 1, sygnalizującą błąd. Procedura nie czeka w takim przypadku na zwolnienie miejsca w buforze.
Procedura wysyłania cyfry szesnastkowej puthexdigit zamienia wartość tetrady na jej reprezentację szesnastkową w kodzie ASCII, a następnie wstawia ją do bufora przy użyciu funkcji USART1_put.
Procedura obsługi przerwania timera
W procedurze zadeklarowano zmienną count – licznik zliczający naciśnięcia przycisku.
Procedura obsługi timera w przypadku wykrycia naciśnięcia przycisku inkrementuje licznik i wstawia do bufora nadawczego jego reprezentację znakową, zakończoną sekwencją końca wiersza. Równocześnie jest zaświecana niebieska dioda, sygnalizująca naciśnięcie przycisku.
/* STM32F0DISCOVERY tutorial USART1 interrupt-driven Tx gbm, 02'2013 */ #include "stm32f0xx.h" //======================================================================== // defs for STM32F05x chips #define GPIO_MODER_OUT 1 #define GPIO_MODER_AF 2 // keep SWD pins #define GPIOA_MODER_SWD (GPIO_MODER_AF <<(14 <<1) | GPIO_MODER_AF <<(13 <<1)) #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 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 control {&RCC->APB1ENR, RCC_APB1ENR_TIM3EN}, {&RCC->APB2ENR, RCC_APB2ENR_USART1EN}, // 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_TE | USART_CR1_RE | USART_CR1_UE}, // enable // 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} }; //======================================================================== int main(void) { writeregs(init_table); __WFI(); // go to sleep } //======================================================================== // UART1 output buffer #define U1OBSIZE 32 static uint8_t u1_outbuf[U1OBSIZE]; static uint8_t u1_obiptr = 0, u1_oboptr = 0; //======================================================================== void USART1_IRQHandler(void) { if (USART1->ISR &USART_ISR_TXE) // ready to send { uint32_t next_optr = (u1_oboptr + 1) % U1OBSIZE; if (u1_obiptr == next_optr) USART1->CR1 &= ~USART_CR1_TXEIE; // nothing more to send, disable int USART1->TDR = u1_outbuf[u1_oboptr]; u1_oboptr = next_optr; } } //======================================================================== static _Bool UART1_put(uint8_t c) { uint32_t next_iptr = (u1_obiptr + 1) % U1OBSIZE; if (next_iptr == u1_oboptr) return 1; // buffer full - signal overflow u1_outbuf[u1_obiptr] = c; u1_obiptr = next_iptr; USART1->CR1 |= USART_CR1_TXEIE; return 0; // ok } //======================================================================== static void puthexdigit(uint8_t v) { v &= 0xf; UART1_put(v + (v <= 9 ? '0' : 'A' - 10)); } //======================================================================== 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; static uint32_t counter = 0; uint32_t pwmval; TIM3->SR = ~TIM_SR_UIF; // clear interrupt flag if ((++ tdiv &3) == 0) { // 100 Hz if ((bstate = (bstate <<1 &0xf) | (BUTTON_PORT->IDR >>BUTTON_BIT &1)) == 1) { int i; // button pressed - increment counter blue_led_timer = BTN_ACK_PERIOD; blue_target = LED_MAX; counter ++; for (i = 7; i >= 0; i --) puthexdigit(counter >>(i * 4)); UART1_put('\r'); UART1_put('\n'); } if (blue_led_timer) { if (-- blue_led_timer == 0) blue_target = LED_DIM; } } if ((pwmval = BLUE_LED_PWM) != blue_target) BLUE_LED_PWM = pwmval