01 kwietnia 2017

Odmierzanie czasu

Tym razem coś łatwiejszego, czyli odmierzanie czasu wykorzystując licznik SysTick. W poprzednich programach do wprowadzania opóźnień używaliśmy zwykłych pętli. Takie rozwiązanie było proste i działało, ale ciężko było precyzyjnie ustawić czas opóźnienia. Sprzętowy licznik sprawdzi się w takiej roli znacznie lepiej.

Na początek małe przypomnienie - nasz mikrokontroler pracuje na wbudowanym generatorze RC o częstotliwości (około) 8MHz.
Gdy już to wiemy, możemy właściwie od razu uruchomić licznik systemowy (SysTick). W dołączonych plikach CMSIS znajdziemy funkcję pomocniczą SysTick_Config. Miało nie być bibliotek, ale zacznijmy nieco na łatwiznę - piszemy:

SysTick_Config(8000);

Parametr wywołania to podzielnik częstotliwości pracy procesora, czyli dzielimy 8MHz przez 8000 - i po karkołomnych przejściach matematycznych obliczamy częstotliwość pracy naszego licznika: 1kHz (czyli okres 1ms).
Teraz wystarczy dodać coś do sprawdzenia, czy mamy rację. Wysterujemy wyjście PA5 i sprawdzimy za pomocą analizatora logicznego, czy wszystko działa jak chcieliśmy. Kod sterujący wyjściem dodajemy do funkcji obsługi przerwania SysTick_Handler. Program wygląda więc następująco:

void SysTick_Handler(void)
{
static uint8_t led_state = 0;

if (led_state == 0) {
GPIOA->BSRR = GPIO_BSRR_BS5;
led_state = 1;
} else {
GPIOA->BRR = GPIO_BRR_BR5;
led_state = 0;
}
}

int main(void)
{
/* Enable the GPIO Clock */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

// PA5 - LED
GPIOA->CRL &= ~GPIO_CRL(GPIO_MASK, 5);
GPIOA->CRL |= GPIO_CRL(GPIO_OUT_PP_50MHZ, 5);

// PC13 - button
GPIOC->CRH &= ~GPIO_CRH(GPIO_MASK, 13);
GPIOC->CRH |= GPIO_CRH(GPIO_IN_FLOAT, 13);

SysTick_Config(8000);

while (1) {
}

}

Sprawdzamy, czy wszystko działa jak oczekiwaliśmy:


I jak widać jest prawie idealnie. Zamiast 1ms mamy, 1,001 ms - ale generator RC nie jest zbyt dokładny więc nie powinniśmy narzekać.
Teraz możemy pozbyć się SysTick_Config, w końcu miało nie być bibliotek. Zaglądamy do jej treści, wyrzucamy co niepotrzebne i zostaje nam:

SysTick->LOAD  = 8000 - 1;
SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

Do rejestru LOAD wpisujemy nasz podzielnik częstotliwości pomniejszony o 1 (ponieważ licznik liczy od zera). Flagi ustawiane w rejestrze CTRL to kolejno:

  • wybór zegara źródłowego (zegar procesora, bez podzielnika)
  • uruchomienie przerwania
  • uruchomienie timera

Warto na chwilę przyjrzeć się fladze SysTick_CTRL_CLKSOURCE_Msk. Gdybyśmy ją skasowali, nasz licznik byłby zasilany zegarem o częstotliwości 8x niższej niż procesor. Czyli moglibyśmy napisać kod tak:

SysTick->LOAD  = 1000 - 1;

SysTick->CTRL  = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

Program działa tak samo, ale pokazuje że gotowe funkcje czasem ograniczają. Bez nich mamy dostęp do wszystkich możliwości mikrokontrolera, a nie tylko tych które udostępnił nam autor biblioteki.

Teraz, gdy już poznaliśmy działanie licznika SysTick, możemy go wykorzystać do napisania prostej i może nie do końca doskonałej funkcji opóźniającej. Utworzymy jako zmienną globalną licznik, który będziemy zmniejszać po wystąpieniu przerwania.
Cały kod wygląda następująco:


volatile uint32_t delay_counter = 0;

void SysTick_Handler(void)
{
if (delay_counter)
delay_counter--;
}

void delay_ms(uint32_t ms)
{
delay_counter = ms;
while (delay_counter);
}

int main(void)
{
/* Enable the GPIO Clock */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

// PA5 - LED
GPIOA->CRL &= ~GPIO_CRL(GPIO_MASK, 5);
GPIOA->CRL |= GPIO_CRL(GPIO_OUT_PP_50MHZ, 5);

// PC13 - button
GPIOC->CRH &= ~GPIO_CRH(GPIO_MASK, 13);
GPIOC->CRH |= GPIO_CRH(GPIO_IN_FLOAT, 13);

SysTick->LOAD  = 1000 - 1;
SysTick->CTRL  = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

while (1) {
GPIOA->BSRR = GPIO_BSRR_BS5;
delay_ms(100);
GPIOA->BRR = GPIO_BRR_BR5;
delay_ms(100);
}

}

Nasza funkcja delay_ms jest bardzo przydatna, będziemy mogli wykorzystać ją jeszcze wielokrotnie. Na wszelki wypadek jeszcze raz sprawdzamy, czy opóźnienia są poprawne:


I jak widzimy wszystko działa dokładnie jak należy.




1 komentarz:

  1. Dotarłem jak na razie do tego etapu, pominąłem przerwania z wejścia i wszystko działa. Jednak nie udało mi się zrobić wejścia jako np.: pull-up bo tabela nr 20 podaje, że trzeba wpisać 0 lub 1 do portu PxODR, ale nie wiemjak się do tego zabrać. Reszta śmiga. Czytanie tego bloga to przyjemność :)

    OdpowiedzUsuń