LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

[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