fbpx

Na razie w taki bardzo prosty i blokujący sposób z wykorzystaniem właśnie blokującego opóźnienia.

👉 Chcesz nauczyć się programowania STM32 na rejestrach w pełni? Zapraszam do mojego pełnego kursu na ten temat: https://stm32narejestrach.pl

Seria STM32 na Rejestrach na YouTube

Wpisy te powstają równolegle do serii na moim YouTube o tej samej tematyce. Jeśli wolisz wersję video to zapraszam Cię właśnie tam. Artykuły te są skrótem z tego, co pokazuję na YouTube.

Link do całej Playlisty Youtube

Podłączenie przycisku do GPIO

Pierwsze co musimy robić, to skonfigurować odpowieni pin GPIO na tryb wejściowy. Przy miganiu diodą korzystaliśmy z wyjścia, czyli Output. Teraz potrzebujemy czytać stan wejściowy, czyli Input.

Na płytce NUCLEO-C031 mamy jeden przycisk, który jest do dyspozycji użytkownika. Musimy dowiedzieć się, na którym pinie się znajduje i w jakiej konfiguracji jest on podłączony. Tego oczywiście dowiemy się ze schematu do Nucleo.

W NUCLEO-C031 przycisk puszczony powoduje stan wysoki na pinie PC13 poprzez rezystor pull-up. Wciśnięcie przycisku spowoduje zwarcie do masy.

Tutaj obok jest jeszcze kondensator C16 i rezystor R26, które są sprzętowym układem eliminacji drgań styków. Sprawdź lepiej jak jest u Ciebie jeśli masz inne Nucleo.

Może być odwrotnie jak np w NUCLEO-G474RE. Lepiej jest takie rzeczy sprawdzać i potwierdzać niż wierzyć mi w każde słowo.

Konfiguracja rejestrów

Co wiemy o przycisku? Po pierwsze to, że znajduje się on na pinie PC13. Musimy skonfigurować PORTC i jego 13 pin do działania jako GPIO Input. Będzie to bardzo podobna czynność jak przy diodzie LED. Zrobimy od razu odpowiednią funkcję konfiguracyjną.

Nie musimy wymyślać tej ścieżki. Będziemy się wzorowali na tym, co zrobiliśmy dla GPIO Output. Pamiętaj, że musimy mieć otwarty Reference Manual. Bez niego nie damy rady!

  1. Pierwszą i najważniejszą rzeczą jest włączenie zegara dla potrzebnego nam portu. Bez tego on nie zadziała. Przycisk jest na porcie C. Włączamy go analogicznie do portu A jak przy diodzie LED.
  2. Następnie ustalamy tryb działania pinu w rejestrze MODER. Teraz ma to być Input. Nie zapominaj, że działamy na porcie C, a nie A. Nie kopiuj bezmyślnie z diody 🙂
  3. Output Type nas nie interesuje, bo nie jesteśmy wyjściem. Tak samo Output Speed. Te ustawienia nie mają żadnego wpływu na GPIO Input. Można je spokojnie pominąć.

    Skąd to wiem? Istnieją takie dodatkowe dokumenty jak Aplication Note. Opisują one użycie poszczególnych peryferiów. Dla GPIO jest taki dokument (>o tutaj<). Możemy w nim znaleźć zapis, że bufor wyjściowy GPIO jest całkowicie odłączony, gdy GPIO ustawione jest jako wejście.
  4. Interesuje nas za to ustawienie pull-up i pull-downm czyli w skrócie PU/PD. Jeśli zerkniemy na schemat blokowy GPIO, to zauważymy, że te rezystorki są przy samym pinie wyjściowym.

    Tyczą się one zarówno wejścia jak i wyjścia. Czy my musimy je ustawiać? Niekoniecznie. Mamy już sprzętowy Pull-up położony ja płytce. Nie potrzebujemy włączać kolejnego Pull-upa w mikrokontrolerze. Zostawiamy wartość domyślną, czyli zero. Możemy to wpisać, ale nie jest to konieczne.

I to tyle jeśli chodzi o skonfigurowanie pinu pod przycisk. Oto jak wygląda nasz konfiguracja:

// PC13 - Button
// 0 - Pushed
// 1 - Released / Idle
void ConfigureButton(void)
{
	// Enable Clock for PORTC
	RCC->IOPENR |= RCC_IOPENR_GPIOCEN;

	// Configure GPIO Mode - Input
	GPIOC->MODER &= ~(GPIO_MODER_MODE13);
	
	// Configure Pull-up/Pull-down - no PU/PD
	GPIOC->PUPDR &= ~(GPIO_PUPDR_PUPD13);
}

Czytanie GPIO Input

Teraz zastanówmy się jak go odczytać i zareagować na to co się na nim dzieje.

Przy wyjściu GPIO mieliśmy dwie drogi (tak naprawdę 3, ale ta trzecia jest trochę bardziej zaawansowana, więc ją pominąłem). Przy czytaniu zwykłego wejścia GPIO nie mamy już takich obfitych możliwości i mamy tylko jeden sposób.

Musimy skorzystać z rejestru IDR (Input Data Register), który to przechowuje informacje i stanie wejść całego portu. Zauważ, że jego pola są oznaczone jako r co oznacza tylko do odczytu.

Musimy z niego wyciągnąć informację o jednym, konkretnym bicie. Jak?

  1. Wziąć całą wartość rejestru
  2. Nałożyć maskę bitową z interesującym nas bitem

Uzyskamy w ten sposób wartość logiczną, która odpowiada za stan na maskowanym przez nas bicie.

  1. Wartość 0 = stan niski
  2. Co innego niż 0 = logiczne TRUE, czyli stan wysoki

W pierwszej kolejności będziemy zapalali diodę zgodnie ze stanem przycisku. Wciśnięty = zapalona, puszczony = zgaszona.

if( GPIOC->IDR & (1<<13) )

 to sprawdza nam stan na pinie 13 rejestru IDR. Można oczywiście też inaczej:

if( GPIOC->IDR & GPIO_IDR_ID13 )

a można też napisać swoją definicję pinu nr 13.

#define PC13 (1<<13)
if( GPIOC->IDR & PC13 )

Musisz pamiętać jaki stan na pinie PC13 jest w jakiej pozycji przycisku. Tutaj jest “odwrotnie” niż byśmy orientacyjnie chcieli. Wciśnięcie, czyli akcja powoduje stan niski. Puszczenie stan wysoki.

Można byłoby napisać makro, które powie nam, czy przycisk jest wciśnięty. Wtedy nie będziemy się zastanawiali jaki stan powoduje wciśnięcie, tylko rozważamy już na poziomie wyżej czy przycisk był wciśnięty.

#define BUTTON_PRESSED (!(GPIOC->IDR & PC13))

Odwracając logikę sprawdzenia uzyskujemy to, co chcieliśmy. Przeanalizujmy to makro.

Wyciągamy informację o 13 bicie z rejestru GPIOC IDR. Jeśli tam jest zero, to oznacza wciśnięcie. My byśmy chcieli, aby to makro mówiło nam TRUE na wciśnięcie. Skoro dostaliśmy zero, a na puszczenie dostaniemy coś innego niż zero to jest źle. Trzeba odwrócić logikę operatorem negacji logicznej. Czyli odwracamy logikę:

  1. Jeśli trafi zero na bicie 13, to jest wciśnięcie. Negacja zera = TRUE
  2. Jeśli trafi jeden na bicie 13, to jest puszczenie. Negacja nie-zera = FALSE

Mamy to, co chcieliśmy.

Zmiana stanu na wciśnięcie

Pójdźmy kawałek dalej. Chcemy teraz wykonać zmianę stanu na diodzie na każde wciśnięcie przycisku.

Musimy napisać makro, które zmienia stan pinu wyjściowego. Służy do tego standardowa operacja XOR na tym bicie. Nie będziemy wchodzili w szczegóły. Ważne, że działa. Zapraszam do mojego kursu z języka C, w którym wchodzimy właśnie w takie szczegóły.

Tylko na czym wykonamy zmianę bitu? Nie możemy na rejestrze atomowym, bo nie daje on takiej możliwości. Trzeba skorzystać ze “zwykłego” rejestru GPIO ODR i w nim zmienić stan odpowiedniego bitu na przeciwny. Jest to więcej niż jedna operacja na procesorze, ale daje radę.

#define LD4_TOGGLE GPIOA->ODR ^= GPIO_ODR_OD5

Debounce przycisku

Działa jak chcemy, ale nie do końca. Stan diody jakby czasami nie zmieniał się, albo jakoś tak losowo. Są dwa powody, z czego jeden tutaj szczególny:

  1. Mityczne drgania styków. Przycisk mechaniczny posiada coś takiego jak drgania styków. Chodzi o to, że puszczony przycisk może jeszcze zadrżeć i zmienić stan na pinie GPIO. Na Nucleo mamy sprzętową eliminację tego, więc to nie jest nasz główny problem.

    Jednak napiszemy kod, który również to eliminuje i powiem Ci później dlaczego.
  2. Naszym głównym problemem jest działanie programu samo w sobie. Mamy program, który w zasadzie nic nie robi. Sprawdza przycisk i jeśli jest wciśnięty, to zmienia stan na diodzie. Problem polega na tym, że pętla główna robi dziesiątki tysięcy jak nie czasem miliony obiegów na sekundę.

    I te dziesiątki tysięcy razy na sekundę zmieniamy stan na diodzie jeśli przycisk jest wciśnięty. Raz nam wypadnie puszczenie przycisku na parzystej liczbie, raz nie. To jest cały sekret tego dlaczego to tak dziwnie działa.

dwie metody na to, aby eliminować takie zachowanie. Pierwsza to podwójne sprawdzenie po odczekaniu krótkiego odcinka czasu. Druga to maszyna stanów w połączeniu z oczekiwaniem. To już jest bardziej zaawansowane i nie będziemy się tym zajmowali w tej serii. Szczegółowo omówiłem to w moim kursie programowania STM32 na HALu.

Jak odczekać? Możemy użyć Delaya na SysTicku którego pisaliśmy w poprzenich odcinkach.

  1. Sprawdzamy, czy było wciśnięcie
  2. Czekamy 20-50 ms
  3. Sprawdzamy drugi raz czy nadal mamy wciśnięty przycisk
  4. Jeśli tak, to wykonujemy akcję.

Napiszmy to.

// Check if button was pressed
if( BUTTON_PRESSED )
{
	Delay(50); // Dummy debounce

	if( BUTTON_PRESSED ) // Check button again
	{
		// Toggle LED
		LD4_TOGGLE;
	}
}

Ma to jedną, sporą wadę. Blokujemy pracę procesora na 50 milisekund. Niestety użyliśmy blokującego czekania to mamy za swoje.

Druga wada jest taka, że ciągle trzymając przycisk nadal pętla zawróci i ponownie wykona ten kod. Tylko z 50 milisekundowym opóźnieniem za każdym razem. Dlatego rozwiązanie na maszynie stanów jest dużo lepsze.

Jednak to nie jest takie mocno beznadziejne. Na szybkie wciśnięcia podziała bardzo skutecznie i do tego bym to wykorzystywał.

Ok, ale dlaczego eliminujemy drgania styków przez odczekanie w progamie, skoro mamy filtr sprzętowy? Przed nieumyślnym wciśnięciem. Zabezpieczamy się tym przed przypadkowym muśnięciem przycisku.

Człowiek w porównaniu do mikrokontrolera jest wolny. Wszystkie jego akcje trwają długo. Więc, aby dopasować interfejs do człowieka po prostu spowalnia się reakcje na jego czynności. Mamy wtedy pewność, że były to akcje świadome, a nie np. komuś coś spadło na przycisk.

Dopóki nie puszczę

Jest jeszcze jedna rzecz, którą możemy dodać do naszego kodu, aby mieć pewność, że akcja wykona się tylko raz. Niestety jest to kolejny element blokujący więc od razu ostrzegam przed jego nadmiernym używaniem.

Możemy postawić pustą pętle, która będzie aktywna dopóki nie puścimy przycisku. Możemy to wykonać na pętli while.

// Check if button was pressed
if( BUTTON_PRESSED )
{
	Delay(50); // Dummy debounce

	if( BUTTON_PRESSED ) // Check button again
	{
		// Toggle LED
		LD4_TOGGLE;
		
		while(BUTTON_PRESSED){} // Wait for button release
	}
}

Zaletą jest to, że tylko raz wykona się TOGGLE. Wadą natomiast jest to, że trzymając przycisk blokujemy działanie całego programu. Tu jest takie niebezpieczeństwo, że procesor będzie zablokowany dopóki nie puścisz przycisku.

Na to również lekarstwem jest zbudowanie obsługi przycisku na maszynie stanów, którą omawiam w moim pełnym kursie STM32 dla Początkujących. Są to już bardziej związane technikami programowania niż z samym programowaniem STM32. W tej serii nie będziemy się tym zajmowali. Tutaj poznajemy jedynie pracę na rejestrach STM32.

Spróbuj pobawić się tym kodem i sprawdź jak się zmienia reakcja w zależności od długości czasu oczekiwania. W kolejnym odcinku pokażę Ci sposób na oczekiwanie, ale bez blokowania pracy mikrokontorlera. Powodzenia!

Podsumowanie

Czytanie przycisku na rejestrach sprowadza się do odczytania tylko jednego rejestru. Oczywiście sam pin musi być uprzednio skonfigurowany jako wejście GPIO. To dalsze czynności, czyli co z tą inormacją zrobimy sprawiają najwięcej trudności. To w jaki sposób obsługujemy wciśnięcie jest już niezależne od rodzaju mikrokontrolera i robi się wszędzie podobnie. Według mnie najlepiej na prostej maszynie stanów.

W kolejnym wpisie zajmiemy się Timerem Programowym. Jest to uniwersalny sposób na multitasking na mikrokontrolerach. Pozwala na współdziałanie wielu zadań „jednocześnie”.

Daj znać w komentarzu czy Ci się podobał ten wpis! Może masz jakąś propozycję co pokazać w ramach cyklu STM32 na Rejestrach? Podziel się tym artykułem ze znajomymi.

Zapraszam Cię również do mojego sklepu, gdzie kupisz ciekawą elektronikę do programowania jak np. NUCLEO-C031C6, z którego korzystamy w tej serii: https://sklep.msalamon.pl

Projekt z tego artykułu znajdziesz na: https://github.com/lamik/stm32narejestrach_4

Podobne artykuły

.

0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *