20 marca 2017

Odczyt stanu wejścia

Poprzednio zobaczyliśmy jak wygląda konfiguracja portów wejścia-wyścia ciągle testując sterowanie diodą LED, czas na wypróbowanie czegoś nowego. Na płytce Nucleo znajdziemy jeden przycisk podłączony do wejścia mikrokontrolera, w ramach kolejnych eksperymentów odczytamy jego stan.

Najpierw musimy sprawdzić do którego wyprowadzenia podłączony jest przełącznik. W dokumentacji płytki znajdziemy bez problemu potrzebną nam informację - jest to pin PC13. Oznacza to, że tym razem odwołamy się do portu C. Oznacza to również, że musimy najpierw ten port włączyć. Ponieważ podobnie jak port A jest on podłączony do magistrali APB2, możemy oba porty uruchomić jedną instrukcją:

/* Enable the GPIO Clock */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

Teraz wybierzmy konfigurację, którą chcemy ustawić. Na pewno chcemy, żeby PC13 był wejściem. Mamy więc do wyboru tryb analogowy, wysoką impedancję, albo podciągnięcie do masy lub zasilania. Pierwsza opcja nie pasuje do wejścia cyfrowego, a takie będziemy wykorzystywać. Żeby więcej dowiedzieć się o podłączonym przycisku oglądamy schemat płytki Nucleo:

Jak widać na płytce znajdziemy już zewnętrzny rezystor podciągający. Wobec tego wykorzystamy wejście wysokiej impedancji. Nasza konfiguracja to MODE[1:0] = 00b, CNF[1:0] = 01b.
Przy okazji mała dygresja - jest to domyślne ustawienie portu, więc właściwie nie musimy nic konfigurować. Ale w ramach ćwiczenia i poznawania układu ustawimy konfigurację sami:

CLEAR_BIT(GPIOC->CRH, GPIO_CRH_MODE13_1); // MODE=00b, input
CLEAR_BIT(GPIOC->CRH, GPIO_CRH_MODE13_0);
CLEAR_BIT(GPIOC->CRH, GPIO_CRH_CNF13_1); // CNF=01b, floating

SET_BIT(GPIOC->CRH, GPIO_CRH_CNF13_0);

Tym razem konfigurujemy pin z zakresu 8 do 15, więc zamiast rejestru CRL wykorzystamy CRH. Do napisania programu potrzebujemy już tylko rejestru, który będzie odzwierciedlał stan pinu - jest to rejestr IDR. 
Cały program, który zapala diodę podczas naciskania przycisku wygląda następująco:

#include "stm32f10x.h"

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

// PA5 - LED
SET_BIT(GPIOA->CRL, GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1); // MODE=11b, output max 50MHz
CLEAR_BIT(GPIOA->CRL, GPIO_CRL_CNF5_0 | GPIO_CRL_CNF5_1); // CNF=00b, output push-pull

// PC13 - button
CLEAR_BIT(GPIOC->CRH, GPIO_CRH_MODE13_1); // MODE=00b, input
CLEAR_BIT(GPIOC->CRH, GPIO_CRH_MODE13_0);
CLEAR_BIT(GPIOC->CRH, GPIO_CRH_CNF13_1); // CNF=01b, floating
SET_BIT(GPIOC->CRH, GPIO_CRH_CNF13_0);

while (1) {
if ((GPIOC->IDR & GPIO_IDR_IDR13) == 0)
GPIOA->BSRR = GPIO_BSRR_BS5; // set PA5
else
GPIOA->BRR = GPIO_BRR_BR5; // reset PA5
}
}

Użyliśmy raptem dwóch pinów, ale już wyraźnie widać, że ustawianie każdego pinu konfiguracyjnego oddzielnie jest niewygodne i sprawia, że program jest mało czytelny. Na początek zdefinujemy stałe dla każdego trybu:

#define GPIO_IN_FLOAT 0x4
#define GPIO_IN_AN 0x0
#define GPIO_IN_PULLUP 0x8

#define GPIO_OUT_PP_2MHZ 0x2
#define GPIO_OUT_PP_10MHZ 0x1
#define GPIO_OUT_PP_50MHZ 0x3
#define GPIO_OUT_OC_2MHZ 0x6
#define GPIO_OUT_OC_10MHZ 0x5
#define GPIO_OUT_OC_50MHZ 0x7
#define GPIO_AF_PP_2MHZ 0xa
#define GPIO_AF_PP_10MHZ 0x9
#define GPIO_AF_PP_50MHZ 0xb
#define GPIO_AF_OC_2MHZ 0xe
#define GPIO_AF_OC_10MHZ 0xd
#define GPIO_AF_OC_50MHZ 0xf

Przyda nam się jeszcze maska do zerowania wszystkich bitów jednocześnie:

#define GPIO_MASK 0xf

Teraz możemy napisać dwa pomocnicze makra, które będą wykonywały przesunięcie bitowe na pozycję w rejestrze związaną z wybranym pinem. Jako pierwszy parametr wystarczy, że podamy stałą opisującą tryb, a jako drugi parametr numer pinu. Makra wyglądają następująco:

#define GPIO_CRL(x, pin) ((x) << (4 * (pin)))
#define GPIO_CRH(x, pin) ((x) << (4 * ((pin) - 8)))

Pierwsze jest przeznaczone dla pinów o numerach od 0 do 7 i oblicza wartość dla rejestru CRL, drugie obsługuje piny 8 - 15, a obliczoną wartość należy wpisać do CRH. Użycie tych makr jest relatywnie łatwe:

// 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);

Najpierw resetujemy wszystkie pola odpowiedzialne za konfigurację pinu, następnie ustawiamy wybrany tryb. Pozostała część programu nie wymaga modyfikacji:

#include "stm32f10x.h"

#define GPIO_MASK 0xf

#define GPIO_IN_FLOAT 0x4
#define GPIO_IN_AN 0x0
#define GPIO_IN_PULLUP 0x8

#define GPIO_OUT_PP_2MHZ 0x2
#define GPIO_OUT_PP_10MHZ 0x1
#define GPIO_OUT_PP_50MHZ 0x3
#define GPIO_OUT_OC_2MHZ 0x6
#define GPIO_OUT_OC_10MHZ 0x5
#define GPIO_OUT_OC_50MHZ 0x7
#define GPIO_AF_PP_2MHZ 0xa
#define GPIO_AF_PP_10MHZ 0x9
#define GPIO_AF_PP_50MHZ 0xb
#define GPIO_AF_OC_2MHZ 0xe
#define GPIO_AF_OC_10MHZ 0xd
#define GPIO_AF_OC_50MHZ 0xf

#define GPIO_CRL(x, pin) ((x) << (4 * (pin)))
#define GPIO_CRH(x, pin) ((x) << (4 * ((pin) - 8)))

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);

while (1) {
if ((GPIOC->IDR & GPIO_IDR_IDR13) == 0)
GPIOA->BSRR = GPIO_BSRR_BS5; // set PA5
else
GPIOA->BRR = GPIO_BRR_BR5; // reset PA5
}
}

Wiemy już jak w miarę łatwo ustawiać konfigurację pinów - to bardzo ważna umiejętność, którą będziemy wykorzystywać w każdym programie. Odnośnie portów zostały nam jeszcze dwie ważne rzeczy do omówienia - sterowanie stanem wyjść oraz przerwania. Wrócimy do tych tematów później.


Brak komentarzy:

Prześlij komentarz