czwartek, 17 marca 2011

ARM : Przykładowy projekt wykorzystujący porty GPIO


Autor: Deucalion
Redakcja: Dondu


Artykuł jest częścią cyklu: Kurs ARM: Spis treści

To już ostatnia część serii dotyczącej portów I/O w mikrokontrolerze LPC1114, ale tym razem od strony praktycznej. Poniżej znajduje się listing prostego programu demonstrujący jak można korzystać z portów GPIO.

W ramach ćwiczeń proponuję prześledzić dokładnie cały program i spróbować znaleźć inne rozwiązanie problemu.


Plik main.c

    #include "LPC11xx.h"
    #include "syscon.h"
    #include "flashcon.h"

    #include "display.h"
    #include "keys.h"

    /* Program demonstrujący używanie portów GPIO w uC LPC1114. Funkcją programu
       jest wyświetlanie stanu licznika na poczwórnym multipleksowanym wyświetlaczu
       siedmiosegmentowym, którego zawartość zmienia się z częstotliwością 10Hz po
       naciśnięciu jednego z dwóch klawiszy.

       Cały port P2 steruje wyświetlaczem siedmiosegmentowym ze wspólną anodą.
       Piny P2.0 - P2.7 sterują segmentami bezpośrednio poprzez rezystory 82R,
       segmenty załączane stanem niskim. Piny P2.8 - P2.11 sterują anodami
       wyświetlaczy poprzez tranzystor PNP. Stan niski aktywuje dany wyświetlacz.

       Piny P3.0 i P3.1 służą jako wejścia odczytujące stan klawiszy KEY1 i KEY2.
       Przytrzymanie klawisza KEY1 (stan niski) powoduje zwiększanie licznika,
       przytrzymanie klawisz KEY2 (stan niski) powoduje zmniejszanie licznika.
    */

    // Zmienna wykorzystywana jako licznik o zakresie 0 - 9999
    static int16_t DispCnt;

    // Zmienna reprezentująca bufory wyświetlaczy
    static TDisplay Display =
    {   
        // Inicjalizacja buforów do wyświetlania 0000 po uruchomieniu
        .buf1x4 = DisplayFillPatern(_0) 
    };

    // tablica odwzorowująca segmenty dla danej cyfry
    const static uint8_t digsmap[] = { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 };

    //******************************************************************************
    //                       Funkcja inicjalizujaca procesor
    //******************************************************************************
    void
    LPC_Init( void )
    {
        // Włączenie oscylatora systemowego
        LPC_SYSCON->PDRUNCFG &= ~PDRUNCFG_SYSOSC_PD;

        // Zwłoka na ustabilizowanie oscylatora poprzez wieleokrotny odczyt rejestru
        for( int i = 0; i < 1000; i++)
        {
            LPC_SYSCON->PDRUNCFG;
        }

        // Przełaczenie źródła sygnału taktującego PLL na zew. oscylator
        LPC_SYSCON->SYSPLLCLKSEL = SYSPLLCLKSEL_SYSOSC;

        // Potwierdzenie przełączenia źródła taktowania
        // poprzez wygenerowania zmiany bitu z 0 na 1
        LPC_SYSCON->SYSPLLCLKUEN = 0;
        LPC_SYSCON->SYSPLLCLKUEN = SYSPLLCLKUEN_ENA;

        // Konfiguracja dzielników PLL  10MHz*5*4 = 200MHz
        LPC_SYSCON->SYSPLLCTRL = PLL_M_DIV_5 | PLL_P_DIV_4;

        // Uruchomienie pętli PLL
        LPC_SYSCON->PDRUNCFG &= ~PDRUNCFG_SYSPLL_PD;

        // Oczekiwanie na synchronizacje pętli PLL
        while(!(LPC_SYSCON->SYSPLLSTAT & SYSPLLSTAT_LOCK));

        // Konfiguracja dostępu do pamięci FLASH (3 cykle)
        LPC_FLASHCFG = (LPC_FLASHCFG & ~FLASHTIM_MASK) | FLASHTIM_50MHz;

        // Przełączenie źródła zegara systemowego na PLL
        LPC_SYSCON->MAINCLKSEL = MAINCLKSEL_PLLOUT;

        // Potwierdzenie przełączenia źródła taktowania
        //     poprzez wygenerowania zmiany bitu z 0 na 1
        LPC_SYSCON->MAINCLKUEN = 0;
        LPC_SYSCON->MAINCLKUEN = MAINCLKUEN_ENA;

        // Wyłączenie wewnętrznego oscylatora
        LPC_SYSCON->PDRUNCFG |= PDRUNCFG_IRC_PD;

        // Włączenie taktowania portów i bloku konfiguracji portów
        LPC_SYSCON->SYSAHBCLKCTRL |= AHBCLKCTRL_IOCON | AHBCLKCTRL_GPIO;

        // Cały port P2 jako wyjście
        DISP_PORT->DIR = 0xFFF;

        // Konfiguracja timera systemowego SysTick

        // Przerwanie co 2,5ms (400Hz ; dla PLL = 50MHz)
        SysTick->LOAD = 50000000UL / 400;

        // Włączenie timera
        SysTick->CTRL = 7;

        // Odblokowanie przerwań od timera SysTick
        NVIC_EnableIRQ(SysTick_IRQn);
    }


    //******************************************************************************
    //                Funkcja obsługi przerwania od timera SysTick
    //******************************************************************************
    void
    SysTick_Handler( void )            // 400Hz
    {
        static uint8_t        // 8 bitowe zmienne statyczne
            dispnum,        // numer następnego wyświetlacza
            div;            // dzielnik 400Hz/40 = 10Hz
           
        // Zwiększenie dzielnika
        ++div;
       
        // ograniczenie zakresu dzielnika do 39 (0 - 39)
        div %= 40;
       
        // Wykonanie tylko dla dzielnika równego 0
        if( !div )
        {   
            // Zmienna z tymczasową kopią licznika
            int16_t DispCntCopy = DispCnt;
           
            // Sprawdzenie czy naciśnięty klawisz KEY1
            if( !KEY_PORT->MASKED_ACCESS[ KEY1 ])
            {
                // Klawisz KEY1 naciśnięty
                // Zwiększenie licznika
                DispCnt++;
                // Ograniczenie zakresu licznika do 9999
                DispCnt %= 10000;
            }
            // Sprawdzenie czy naciśnięty klawisz KEY2
            if( !KEY_PORT->MASKED_ACCESS[ KEY2 ])   
            {
                // Klawisz KEY2 naciśnięty
                // zmniejszenie licznika
                DispCnt--;
                // Sprawdzenie zakresu licznika
                if( DispCnt < 0 )
                {
                    //Licznik mniejszy od 0, przewiniecie licznika do 9999
                    DispCnt = 9999;
                }
            }
            // Aktualizacja buforów wyświetlaczy tylko jesli zmiana licznika
            if( DispCntCopy != DispCnt )
            {
                // Tymczasowa kopia licznika wykorzystywana do odwzorowania stanu
                // licznika na wyświetlaczach
                DispCntCopy = DispCnt;
                // Wyczyszczenie zawartości buforów wszystkich wyświetlaczy
                ClearDisplay();
                // Odwzorowanie zawartości licznika na 4 wyświetlaczach
                for( int i = 3; i >= 0 ; i-- )
                {
                    Display.buf4x1[ i ] = digsmap[ DispCntCopy % 10 ];
                    DispCntCopy /= 10;
                }
            }
        }
       
        // Wyłączenie wszystkich wyświetlaczy
        DISP_PORT->MASKED_ACCESS[ DISP_MASK ] = DISP_MASK;
       
        // Przełączenie stanu segmentów dla następnego wyświetlacza
        DISP_PORT->MASKED_ACCESS[ SEG_MASK ] = Display.buf4x1[ dispnum ];
       
        // Włączenie następnego wyświetlacza
        DISP_PORT->MASKED_ACCESS[ DISP_MASK ] = ~(( 1 << dispnum++ ) << 8 );
       
        // Ograniczenie zakresu licznika 0 - 3
        dispnum %= 4;
    }

    //******************************************************************************
    //                           Funkcja pętli głównej
    //******************************************************************************
    int
    main( void )
    {
        LPC_Init();

        while( 1 )
        {
            // Kod pętli głównej
            __WFE();    // Uśpienie procesora
        }
    }


Plik display.h

    #ifndef _DISPLAY_H_
    #define _DISPLAY_H_

    // Deklaracje dotyczące sterowania multipleksowanym wyświetlaczem LED

    // Cały port GPIO2 wykorzystany do sterowania wyświetlaczami
    #define DISP_PORT    LPC_GPIO2

    // Wyświetlacze wspólna anoda sterowane przez tranzystory PNP
    // Stan niski aktywuje wyświetlacze
    #define DISP1       (1 << 8)        // P2.8
    #define DISP2       (1 << 9)        // P2.9
    #define DISP3       (1 << 10)       // P2.10
    #define DISP4       (1 << 11)       // P2.11
    #define DISP_MASK   0xf00

    #define SEGA        (1 << 0)        // P2.0
    #define SEGB        (1 << 1)        // P2.1
    #define SEGC        (1 << 2)        // P2.2
    #define SEGD        (1 << 3)        // P2.3
    #define SEGE        (1 << 4)        // P2.4
    #define SEGF        (1 << 5)        // P2.5
    #define SEGG        (1 << 6)        // P2.6
    #define SEGH        (1 << 7)        // P2.7
    #define SEG_MASK    0x0ff

    // Segmenty sterowane bezpośrednio z procesora przez rezystory 82R
    // Stan niski aktywuje segmenty
    #define _0    (uint8_t)( ~(SEGA | SEGB | SEGC | SEGD | SEGE  | SEGF))
    #define _1    (uint8_t)( ~(SEGB | SEGC))
    #define _2    (uint8_t)( ~(SEGA | SEGB | SEGD | SEGE | SEGG))
    #define _3    (uint8_t)( ~(SEGA | SEGB | SEGC | SEGD | SEGG))
    #define _4    (uint8_t)( ~(SEGB | SEGC | SEGF | SEGG))
    #define _5    (uint8_t)( ~(SEGA | SEGC | SEGD | SEGF | SEGG))
    #define _6    (uint8_t)( ~(SEGA | SEGC | SEGD | SEGE | SEGF | SEGG))
    #define _7    (uint8_t)( ~(SEGA | SEGB | SEGC))
    #define _8    (uint8_t)( ~(SEGA | SEGB | SEGC | SEGD | SEGE | SEGF | SEGG))
    #define _9    (uint8_t)( ~(SEGA | SEGB | SEGC | SEGD | SEGF | SEGG))
    #define _DOT  (uint8_t)( ~(SEGH))

    // TDisplay - Typ definiujący bufory dla zawartości 4 wyświetlaczy LED
    // buf4x1[ 4 ] - zawartość poszczególnych wyświetlaczy
    // buf1x4 - za pomocą jednego wywołania można modyfikować na raz zawartość
    //          wszystkich wyświetlaczy
    typedef union
    {
        uint8_t  buf4x1[ 4 ];
        uint32_t buf1x4;
    }TDisplay;

    // Kilka makr do operacji na wyświetlaczach

    #define ClearDisplay()         ( Display.buf1x4 = ~0 )
    #define DisplayPatern(a,b,c,d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
    #define DisplayFillPatern(a)   DisplayPatern(a, a, a, a)
    #define SetDisplay(a,b,c,d)    ( Display.buf1x4 = DisplayPatern( a, b, c, d ))
    #define FillDisplay(a)         SetDisplay(a, a, a, a)

    #endif


Plik keys.h

    #ifndef _KEYS_H_
    #define _KEYS_H_

    //******************************************************************************
    // Definicje dotyczące klawiszy KEY1 i KEY2. Stan niski po naciśnięciu
    // KEY1 - P3.0 - Zwiększanie licznika (10Hz)
    // KEY2 - P3.1 - Zmniejszanie licznika (10Hz)
    //******************************************************************************
    #define KEY_PORT    LPC_GPIO3

    #define KEY1        (1<<0)
    #define KEY2        (1<<1)
    #define ANYKEY      (KEY1 | KEY2)
    #define KEY_MASK    (KEY1 | KEY2)

    #endif


Pobierz

Cały projekt z powyższym kodem oraz zaktualizowanymi plikami nagłówkowymi.
LPC1114_GPIO.zip lub mirror: LPC1114_GPIO.zip

1 komentarz:

  1. Przydałby się schemacik jak podłączyć. Wiem, wiem, że jest opisane w komentarzu...

    OdpowiedzUsuń