piątek, 11 września 2015

LPC1114 - timery CT16B0, CT16B1 oraz CT32B0 i CT32B1

Mikrokontroler LPC1114 posiada 4 timery (liczniki) ogólnego przeznaczenia, dwa timery  32 bitowe oraz kolejne dwa 16 bitowe. Oczywiście są jeszcze specjalne jak SysTick czy WDT, lecz nie będę ich w tym poście opisywał.
Na rysunku Fig. 80 przedstawiono budowę timera 32 bitowego, budowa timera 16 bitowego jest identyczna poza rozmiarami rejestrów liczących, których wielkość to 16 bitów w związku z czym mogą maksymalnie zliczyć do wartości 65535.
Fig. 80. Budowa timera

Timery 16 bitowe nazywają się CT16B0 i CT16B1,  oprócz zwykłego odliczania czasu pozwalają na generację sygnału PWM wytwarzanego przez blok timera całkowicie sprzętowo. Poza tym mogą być sterowane poprzez wejście przechwytujące (CAP) np. do bardzo precyzyjnego odmierzania czasu trwania impulsów.

Rejestry timera 16 bitowego

Wszystkie rejestry 16 bitowego timera CT16B0 przedstawia tabelka powyżej, podobne rejestry są także w przypadku timera CT16B1 (oczywiście jest inny adres bazowy - więcej w UM). Dla timera CT16B1 nazwy rejestrów są analogiczne, np. TMR16B0IR nazywa się TMR16B1IR.

Rejestr IR - flagi przerwania
Rejestr IR informuję, że nastąpiło przerwanie poprzez ustawienie bitu na konkretnej pozycji, odpowiednio zapalony:
  • bit 0 - przerwanie od rejestru porównywającego MR0
  • bit 1- przerwanie od rejestru porównywającego MR1
  • bit 2 - przerwanie od rejestru porównywającego MR2
  • bit 3 - przerwanie od rejestru porównywającego MR3
  • bit 4 - przerwanie od rejestru przechwytującego CR0
W obsłudze przerwania należy skasować flagę przerwania, ponieważ brak jej skasowania spowoduje ponowne wejście do obsługi przerwania i tak w nieskończoność, zatem mikrokontroler poświęci się tylko obsłudze tego przerwania zamiast wykonywać dalsza cześć programu. Kasując ją informujemy mikrokontroler, że przerwanie został już obsłużone i może powrócić do wykonywania innych instrukcji programu.
Kasowanie flagi następuje poprzez wpisanie 1 na odpowiednim bicie, dlatego należy mieć na uwadze że kasowanie flagi przerwania nie następuje po wpisaniu 0.
LPC_TMR16B0->IR = 2;  // kasuje flage przerwania od rejestru porywnywującego MR1
LPC_TMR16B0->IR = 8  // kasuje flage przerwania od rejestru porywnywującego MR3

Rejestr TCR - strat / stop timera
Rejestr TCR służy do startowania i zatrzymywania timera oraz resetu, czyli zerowania rejestru zliczającego TC.
  • bit 0 - wartość 0 stop timera, wartość 1 start timera
  • bit 1 - ustawienie spowoduje wyzerowanie rejestru TC przy następnym rosnącym zboczu PCLK
  • bity 31:2 - nie należy wpisywać wartości 1 !


Rejestr TC - licznik timera
Rejestr TC, czyli licznik timera jest inkrementowany za każdym cyklem PCLK z uwzględniem preskalera PR + 1. Licznik jest kontrolowany (start/stop) poprze rejestr TCR. 


Rejestr PR - preskaler
Rejestr PR jest odpowiedzialny za podział zegara, który dociera do licznika TC i odpowiada za inkrementacje licznika TC.  Po resecie jego wartość wynosi 0, zatem jak wyżej wspomniałem inkrementacja TC następuje z każdym cyklem PCLK. Dla PR=1 inkrementacja będzię następowała co 2 cykle PCLK, dla PR=2 co 3 cykle, itd.


Rejestr MCR
Rejestr MCR służy do konfiguracji rekacji uC kiedy licznik TC zliczy do wartości wpisanej w danym rejestrze MRx, np. wygenerowania przerwania kiedy wartość licznika TC będzie taka sama co wpisana do rejestru porównującego MR3.
  • bit 0 - ustwienie bitu spowoduje wyzerowanie licznika kiedy TC = MCR0
  • bit 1 - ustawienie powoduje zatrzymanie licznika kiedy TC = MCR0
  • bit 2 - ustawienie powoduje wygenerowanie przerwania kiedy licznik TC = MR0
  • bit 3 - ustwienie bitu spowoduje wyzerowanie licznika kiedy TC = MR1
  • bit 4 - ustawienie powoduje zatrzymanie licznika kiedy TC = MR1
  • bit 5 - ustawienie powoduje wygenerowanie przerwania kiedy licznik TC = MR1
  • bit 6 - ustwienie bitu spowoduje wyzerowanie licznika kiedy TC = MR2
  • bit 7 - ustawienie powoduje zatrzymanie licznika kiedy TC = MR2
  • bit 8 - ustawienie powoduje wygenerowanie przerwania kiedy licznik TC = MR2
  • bit 9 - ustwienie bitu spowoduje wyzerowanie licznika kiedy TC = MR3
  • bit 10 - ustawienie powoduje zatrzymanie licznika kiedy TC = MR3
  • bit 11 - ustawienie powoduje wygenerowanie przerwania kiedy licznik TC = MR3
  • bity 31:12 - nie należy wpisywać wartości 1 !

Rejestry porównujący MR0 - MR3
W przypadku timera 16 bitowego tak jak na powyższym obrazku można wpisać do rejestru porównującego maksymalnie liczbę 16 bitową czyli wartość 65535, natomiast przy timerze 32 bitową wartość 32 bitową.


Poniżej przykład generujący przerwanie co 20 ms,  które zmienia stan pinu P0.5 na przeciwny.

1:  #include "LPC11xx.h"  
2:    
3:  #define PRESCALER (SystemCoreClock / 1000)  
4:    
5:  // obsluga przerwania od CT16B0  
6:  void TIMER16_0_IRQHandler(void)  
7:  {  
8:       LPC_TMR16B0->IR = 2;         // kasowanie flagi przerwania od rejestru porównującego MR1  
9:       LPC_GPIO0->DATA ^= 1<<5;     // zmienia stan na przeciwny na P0.5  
10:  }  
11:    
12:  int main(void) {  
13:       LPC_GPIO0->DIR |= 1<<5;                // ustawia P0.5 jako wyjsciowy  
14:    
15:       LPC_SYSCON->SYSAHBCLKCTRL |= 1<<7;     // wlaczenie zegara dla bloku timera CT16B0  
16:    
17:       // konfiguracja CT16B0  
18:       LPC_TMR16B0->PR = PRESCALER - 1;         // PCLK / 1000 czyli na 1 ms  
19:       LPC_TMR16B0->MR1 = 20;                   // wpisanie do MR1 20 czyli 20 ms  
20:       LPC_TMR16B0->TC = 0;                    // wpisanie do licznika 0, nie jest konieczne bo po resecie i tak TC=0  
21:       LPC_TMR16B0->MCR = (1<<3) | (1<<4);     // wygeneruj perzerwanie i wyzeruj TC kiedy TC=MR1 
22:       LPC_TMR16B0->TCR = 1;                   // start timera CT16B0  
23:    
24:    while(1) {};  
25:    return 0 ;  
26:  }  
 
Należy pamiętać by włączyć taktowanie dla danego bloku timera ponieważ bez niego dany blok nie działa, w przykładzie realizuje to linia nr 15. Linie nr 20 można pominąć ponieważ po restarcie / uruchomieniu mikrokontrolera licznik TC jest zawsze wyzerowany. Zastanawiać może zmienna SystemCoreClock która jest potrzebna do wyliczenia wartości dla preskalera. Zmienna ta jest ustawiana przez funkcję SystemInit jeszcze przed wywołaniem zawartości w głównej funkcjii main. Kiedy nie piszemy programu w środowisku LPCXpresso lub nie korzystamy z szablonu generowanego prze to środowisko należy znać taktowanie uC, jeśli taktujemy częstotliwością 48 MHz zapisać można jak niżej
#define PRESCALER (48000000 / LPC_SYSCON->SYSAHBCLKDIV / 1000)

Wielu początkujący programistów szuka sposobu na realizację opóźnienia



niedziela, 23 sierpnia 2015

printf - przekierowanie w LPCXpresso

Idealnym rozwiązaniem komunikacji mikrokontrolera ze światem zewnętrznym jest funkcja printf i jej podobne. Wystarczy odpowiednio przekierować wyście / wejście na odpowiednie urządzenie i korzystać z jej dobrodziejstw. To ona będzie się "martwić" za odpowiednie wyświetlenie tekstu, liczb całkowitych jak również zmiennoprzecinkowych.
W środowisku programistycznym LPCXpresso istnej możliwość utworzenia projektu z Semihosting, wtedy funkcja printf automatycznie będzie przekazywała rezultat swojego działania do LPCXpresso i jej wynik będzie widoczny podczas debuggowania w IDE. Jednak ta możliwość nie jest tematem postu, więcej nt. można przeczytać na stronie www.lpcware.com/content/faq/lpcxpresso/semihosting

Pierwszym krokiem jaki należy wykonać jest przełączenie projektu żeby korzystał z biblioteki Redlib (nohost), wg. kroków pokazanych na rysunku poniżej


Po przełączeniu biblioteki kod podany poniżej bez problemu powinien się skompilować

#include "LPC11xx.h"

int main(void) {

    printf( "Hello World\r\n" );

    while(1)  {}

    return 0 ;
}


Drugim krokiem jest napisanie własnych funkcji __sys_write, która odpowiada za wysyłanie danych do urządzenia oraz __sys_readc odpowiedzialnej za pobieranie danych z urządzenia. Ponieważ printf zamierzamy przekierować do UART'u należy napisać także funkcje incjalizującą UART.
Ponieważ kod źródłowy inicjalizujący UART zbędnie zajmowałaby zawartość postu przykładowy projekt wraz z funkcjami znajduje się tutaj.

Oto przykładowe ciało funkcji __sys_write
int _sys_write(int iFileHandle, char *pcBuffer, int iLength)
{
    unsigned int i;

    for (i = 0; i < iLength; i++)
    {
        UART_Sendchar(pcBuffer[i]); // wysyla znak na UART
    }
    return iLength;
}

 

Oto przykładowe ciało funkcji __sys_readc
int __sys_readc(void)
{
    char c = UART_Getchar();
    return (int)c;

 

Przykład wykorzystania przekierowania:
#include "LPC11xx.h"
#include <stdio.h>
#include "uart.h"

int main(void) {
    volatile static int i = 0 ;

    UART_Init( 115200 );    // ustawia UART na predkosc 115200

    printf( "Hello World\r\n" );

    while(1) {
        printf( "I=%d\r\n", i++ );
    }
    return 0 ;
}


Jak widać w kodzie powyżej programista musi pamiętać aby  użyć funkcji Uart_Init przed korzystaniem z printf. Elastyczność funkcji printf wiąże się z rozmiarem kodu generowanego przez kompilator, dla kodu przedstawionego powyżej generuje 12336 bajtów, aby zmniejszyć  rozmiar kodu wynikowego można użyć symbolu CR_INTEGER_PRINTF przekazanego do kompilacji który spowoduje, że funkcja printf nie będzie potrafiła wyświetlić liczb zmiennoprzecinkowych (float / double). Dodając do kompilacji symbol CR_INTEGER_PRINTF kod wynikowy zmniejszył się do 7416 bajtów. Jak powiadają prawdziwi programiści embedded "float nie jest potrzebny do życia" :) zawsze można napisać program w ten sposób by uniknąć stosowania liczb zmiennoprzecinkowych.