LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

[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