[2] Kurs: pierwsze kroki z STM32F0DISCOVERY. Zabawy z LED-ami
Przetwornik analogowo-cyfrowy
Mikrokontrolery STM32F051 jest wyposażony w 12-bitowy przetwornik analogowo-cyfrowy, umożliwiający pomiar napięć zewnętrznych, a także wewnętrznego wzorca napięcia odniesienia oraz napięcia generowanego przez wbudowany w mikrokontroler czujnik temperatury. W celu zademonstrowania działania przetwornika użyjemy czujnika temperatury. Oprogramowanie będzie sterowało diodami LED na podstawie odczytu temperatury. Gdy temperatura będzie rosła – zaświeci się dioda zielona. Podczas spadku temperatury będzie świeciła dioda niebieska. W ten sposób użytkownik będzie miał możliwość sterowania świeceniem diod poprzez dotknięcie obudowy mikrokontrolera palcem. Skorzystamy z poprzedniego projektu, zachowując sterowanie PWM z powolnym rozjaśnianiem i ściemnianiem. Gotowy projekt nosi nazwę ADC-ts.
Algorytm
Ponieważ odczyty przetwornika analogowo-cyfrowego zazwyczaj są niestabilne, program powinien zapewniać odpowiednią ich filtrację (uśrednianie). Wyniki pomiarów będą odczytywane ze stałą częstotliwością w przerwaniu timera. Obliczane będą dwie uśrednione wartości odczytów – krótko- i długoterminowa. Sterowanie jasnością diod będzie bazowało na różnicy tych średnich. Zaświecenie diody nastąpi, gdy średnia krótkoterminowa będzie różniła się od średniej długoterminowej nie mniej, niż o wartość progową zadaną poprzez stałą T_DELTA.
Na uwagę zasługuje zastosowany algorytm filtracji wyników pomiarów, nie wymagający przechowywania wyników ostatnich pomiarów. Program przechowuje dwie sumy pomiarów – krótko- i długoterminową. Przy każdym pomiarze od każdej sumy odejmowana jest wartość średnia, obliczona przez podzielenie sumy przez stałą i dodawana jest wartość bieżącego pomiaru. Ponieważ liczba sumowanych wartości próbek jest potęgą liczby 2, dzielenie jest realizowane przez przesunięcie bitowe. Liczby uśrednianych próbek są zdefiniowane przez stałe SHORT_AVG_SHIFT i LONG_AVG_SHIFT – są to logarytmy binarne z liczb uśrednianych wyników pomiarów.
Przy pierwszym pomiarze następuje zainicjowanie obu wartości uśrednionych wynikiem pomiaru.
Zaprogramowanie przetwornika
Przetwornik analogowo-cyfrowy zaimplementowany w kładach STM32F05x jest dość złożony, dlatego warto poświęcić mu nieco więcej uwagi. Producent zaleca przeprowadzenie procedury automatycznej kalibracji przetwornika przy starcie mikrokontrolera. Zarówno kalibracja, jak i przygotowanie przetwornika do pracy zajmują pewien czas (dziesiątki mikrosekund). Aby uniknąć programowego oczekiwania na zakończenie tych czynności warto odpowiednio zaprojektować obsługę przetwornika w przerwaniu timera.
Początkowe programowanie przetwornika składa się z sześciu kolejnych czynności:
- Włączenia przetwornika – zapis do rejestru APB2ENR.
- Wyboru wejścia pomiarowego – czujnika temperatury (kanał 16) w rejestrze CHSELR.
- Określenia okresu próbkowania – rejestr SMPR.
- Włączenia źródła napięcia odniesienia i czujnika temperatury – rejestr CCR.
- Wyboru trybu pracy przetwornika – pomiary ciągłe z automatycznym wyzwalaniem kolejnego pomiaru po odczycie wyniku przetwarzania – rejestr CFGR1.
- Zainicjowania procedury kalibracji – rejestr CR.
Po wykonaniu tych czynności następuje rozpoczęcie kalibracji.
Obsługa przetwornika w przerwaniu timera
Po zainicjowaniu kalibracji przetwornik nie jest jeszcze gotowy do pracy. Uruchomienie przetwornika nastąpi w procedurze obsługi przerwania timera. Podobnie, jak w poprzednim projekcie, procedura ta będzie wywoływana z częstotliwością 400 Hz, jednak większość czynności będzie wykonywana z częstotliwością 100 Hz.
W trakcie działania programu przetwornik ADC będzie kolejno znajdował się w jednym z trzech stanów:
- kalibracji,
- przygotowania do pracy,
- pomiarów.
Po uruchomieniu pomiarów przetwornik pozostanie w stanie pomiarów. Bieżący stan przetwornika jest określany przez oprogramowanie na podstawie wartości rejestrów sterujących. W stanie pomiarów w rejestrze ISR jest ustawiony bit EOC. W stanie przygotowania do pracy bit EOC jest wyzerowany, ale ustawiony jest bit ADRDY. W stanie kalibracji oba wymienione bity rejestru ISR są wyzerowane, a zakończenie kalibracji może być rozpoznane na podstawie zerowych wartości bitów ADCAL i ADEN w rejestrze CR. Sekwencja instrukcji if-then-else zapewnia przejście od kalibracji poprzez włączenie przetwornika do stanu pomiaru.
Po wykryciu zakończenia kalibracji przetwornik zostanie włączony poprzez ustawienie bitu ADEN w rejestrze CR. Instrukcja ta zostanie wykonana tylko jeden raz – w ostatnim bloku else if.
Bezpośrednio po włączeniu przetwornik nie jest jeszcze gotowy do pomiarów. Ponieważ pomiary nie zostały rozpoczęte, bit EOC w rejestrze ISR będzie miał wartość 0, co spowoduje pominięcie pierwszego bloku if i przejście do kolejnego else if, w którym następuje sprawdzenie gotowości przetwornika do rozpoczęcia pomiarów i rozpoczęcie pomiarów poprzez ustawienie bitu ADSTART w rejestrze CR. Instrukcja ta wykona się tylko jeden raz – przy kolejnych wykonaniach będzie już spełniony warunek pierwszego bloku if. W bloku tym jest odczytywany wynik pomiaru, a następnie, w zależności od tego, czy jest to pierwszy pomiar czy kolejny, następuje zainicjowanie albo aktualizacja wartości uśrednionych i wysterowanie LED na ich podstawie. Odczyt wyniku spowoduje wyzwolenie kolejnego pomiaru, którego wynik będzie pobrany w następnym przerwaniu timera.
Sterowanie świeceniem diod
Kolejna instrukcja if w obsłudze przerwania timera służy do zaświecania i gaszenia diod na podstawie odczytów temperatury. Decyzja o stanie diod jest podejmowana na podstawie porównania dwóch średnich – krótko- i długoterminowej. Wartości obu średnich są wyznaczone poprzez przesunięcie w prawo wartości odpowiednich sum, co jest równoważne ich podzieleniu przez liczbę filtrowanych wyników pomiarów
Jeżeli obie średnie pomiarów są równe, diody są przyciemniane. Jeśli średnia krótkoterminowa jest większa o wartość progową lub więcej od średniej długoterminowej, jest rozjaśniana dioda zielona, sygnalizująca wzrost temperatury. Jeśli średnia krótkoterminowa jest mniejsza od średniej długoterminowej o wartość progową lub więcej, rozjaśniana jest dioda niebieska, sygnalizująca spadek temperatury.
/* STM32F0DISCOVERY tutorial TIM3 PWM LED control by ADC temperature sensor gbm, 12'2012 */ #include "stm32f0xx.h" //======================================================================== // defs for STM32F05x chips #define GPIO_MODER_OUT 1 #define GPIO_MODER_AF 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 #define ADC_SMPR_71_5 6 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 //======================================================================== // Timings #define SYSCLK_FREQ HSI_VALUE // PWM constants #define PWM_FREQ 400 // Hz #define PWM_STEPS 50 #define PWM_CLK SYSCLK_FREQ #define PWM_PRE (PWM_CLK / PWM_FREQ / PWM_STEPS) #define BLUE_LED_PWM TIM3->CCR3 #define GREEN_LED_PWM TIM3->CCR4 #define LED_MAX (PWM_STEPS - 1) #define LED_DIM 1 // temperature sensor parms #define LONG_AVG_SHIFT 4 #define SHORT_AVG_SHIFT 8 #define T_DELTA 2 //======================================================================== 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[] = { // port setup {&RCC->AHBENR, RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIOAEN}, // GPIOC, GPIOA { &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 // ADC setup {&RCC->APB2ENR, RCC_APB2ENR_ADC1EN}, // ADC {&ADC1->CHSELR, ADC_CHSELR_CHSEL16}, // ADC {&ADC1->SMPR, ADC_SMPR_71_5}, // ADC {&ADC->CCR, ADC_CCR_TSEN | ADC_CCR_VREFEN}, // ADC {&ADC1->CFGR1, ADC_CFGR1_WAIT | ADC_CFGR1_CONT}, // ADC // ADC cal {&ADC1->CR, ADC_CR_ADCAL}, // ADC // PWM timer setup - TIM3 {&RCC->APB1ENR, 1 <<1}, // TIM3 {(__IO32p)&TIM3->PSC, PWM_PRE - 1}, {(__IO32p)&TIM3->ARR, PWM_STEPS - 1}, // 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 buffer, 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 } //======================================================================== void TIM3_IRQHandler(void) { static uint8_t tdiv = 0; static uint8_t blue_target = LED_DIM, green_target = LED_DIM; static uint32_t long_avg, short_avg; uint32_t val; TIM3->SR = ~TIM_SR_UIF; // clear interrupt flag if ((++ tdiv & 3) == 0) { // 100 Hz if (ADC1->ISR &ADC_ISR_EOC) { val = ADC1->DR; // 0 before first conversion if (short_avg == 0) { // initial measure - set short_avg = val < >SHORT_AVG_SHIFT); long_avg = long_avg + val - (long_avg >>LONG_AVG_SHIFT); if ((short_avg >>SHORT_AVG_SHIFT) == (long_avg >> LONG_AVG_SHIFT)) { // stable blue_target = LED_DIM; green_target = LED_DIM; } else if ((short_avg >>SHORT_AVG_SHIFT) >(long_avg >>LONG_AVG_SHIFT) + T_DELTA) { // warming up blue_target = LED_DIM; green_target = LED_MAX; } else if ((short_avg >>SHORT_AVG_SHIFT) + T_DELTA <(long_avg >>LONG_AVG_SHIFT)) { // cooling blue_target = LED_MAX; green_target = LED_DIM; } } } else if (ADC1->ISR &ADC_ISR_ADRDY) { // ready for conversion ADC1->CR = ADC_CR_ADSTART | ADC_CR_ADEN; // start cont. conversion } else if ((ADC1->CR &(ADC_CR_ADCAL | ADC_CR_ADEN)) == 0) { // calibrated but not enabled yet - enable ADC1->CR = ADC_CR_ADEN; } } if ((val = BLUE_LED_PWM) != blue_target) BLUE_LED_PWM = val < blue_target ? val + 1 : val - 1; if ((val = GREEN_LED_PWM) != green_target) GREEN_LED_PWM = val < green_target ? val + 1 : val - 1; }
Grzegorz Mazur
gbm@ii.pw.edu.pl