[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

Technologie End of Life i bezpieczeństwo sieci – wyzwania Europy związane z tzw. długiem technologicznym
Najczęstsze błędy firm przy wyborze dostawcy energii i jak ich uniknąć
Fotorezystor, czyli czujnik światła dwojakiego działania. Przykład innowacji w automatyce i elektronice możliwej dzięki technologii fotooporników 



