Lekcja 06- Przerwania

Wstęp

Przerwanie to zdarzenie zewnętrzne odbierane przez system, które musi zostać obsłużone przez system. Takim zdarzeniem może być np. wciśnięcie guzika czy odebranie danych przez kartę sieciową. Obsługa przerwania jest wykonywana poprzez odpowiednio zdefiniowaną funkcję. Dzięki użyciu przerwań nie musimy sprawdzać okresowo stanu danej rzeczy(np. guzika) tylko spokojnie oczekujemy na sygnał.

Implementacja

Nie ma chyba sensu się póki co głębiej rozwodzić na przerwaniami, przejdziemy od razu do implementacji.

Na początek zacznijmy od tego co zaimplementujemy, napiszemy sterownik, który będzie zmieniał stan diody w reakcji na naciśnięcie przycisku tact-switch. Będziemy bazować na kodzie z lekcji czwartej.

Zanim zaczniesz implementację podłącz wymagane elementy do swojej płytki. W przypadku BBB dioda niech będzie podłączona do GPIO48, a przycisk do GPIO49, w przypadku RPi4 użyj pinów GPIO23 dla diody oraz GPIO24 dla przycisku. Oczywiście pamiętaj aby użyć rezystora podczas podłączania diody. Druga nóżka przycisku powinna być podłączona do VCC3V3 poprzez rezystor.

Cały kod sterownika wygląda tak:

#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>

//BBB
#define LED 48
#define BUTTON 49
//RPi4
//#define LED 23
//#define BUTTON 24

static int led_state = 0;
static int di;

static irqreturn_t irq_handler(int irq, void *dev_id)
{
	led_state ^= 1;
	gpio_set_value(LED, led_state);
	return IRQ_HANDLED;
}

static int __init helloworld_init(void)
{
	int ret;

	if(!gpio_is_valid(LED)) {
		pr_err("Invalid GPIO pin(%i)!!!\n", LED);
		ret = -ENODEV;
		goto err1;
	}
	gpio_request(LED, "sysfs");
	gpio_direction_output(LED, led_state);
	gpio_export(LED, false);

	if(!gpio_is_valid(BUTTON)) {
		pr_err("Invalid GPIO pin(%i)!!!\n", BUTTON);
		ret = -ENODEV;
		goto err2;
	}
	gpio_request(BUTTON, "sysfs");
	gpio_direction_input(BUTTON);
	gpio_set_debounce(BUTTON, 200);
	gpio_export(BUTTON, false);

	if((ret = request_irq(gpio_to_irq(BUTTON), irq_handler, IRQF_TRIGGER_RISING, "interrupt_test", &di))) {
		pr_err("Cannot register IRQ, ret: %i\n", ret);
		goto err3;
	}

	pr_info("Init succeeded\n");
	return 0;

err3:
	gpio_set_value(BUTTON, 0);
	gpio_unexport(BUTTON);
	gpio_free(BUTTON);
err2:
	gpio_set_value(LED, 0);
	gpio_unexport(LED);
	gpio_free(LED);
err1:
	return ret;
}

static void __exit helloworld_exit(void)
{
	free_irq(gpio_to_irq(BUTTON), &di);

	gpio_set_value(BUTTON, 0);
	gpio_unexport(BUTTON);
	gpio_free(BUTTON);

	gpio_set_value(LED, 0);
	gpio_unexport(LED);
	gpio_free(LED);
	pr_info("Exit succeeded\n");
}

module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL v2");

Jak widać kod nam się nieco wydłużył względem lekcji czwartej, wynika to przede wszystkim z faktu potrzeby zainicjalizowania dodatkowego pinu w funkcji init oraz jego uwolnienia w funkcji exit. Nad samą konfiguracją pinu nie będę się może zbytnio rozwodzić, zwróć uwagę, że w przypadku inicjalizacji pinu do którego jest podłączony przycisk ustawiamy go jako wejście(funkcja gpio_direction_input). Drugą rzeczą na którą musisz zwrócić uwagę to ustawienie debounce’a(nie wiem czy mamy jakieś polskie słowo na to) wynoszący 200 mikrosekund, chodzi o to aby system nie reagował na drgania styków przycisku.

A teraz najważniejsze, samo przerwanie. W funkcji init musimy zarejestrować nasze przerwanie funkcją request_irq. Przyjmuje ona następujące parametry- numer przerwania do obsługi, procedurę obsługi przerwania, flagi, nazwę urządzenia generującego przerwanie oraz „ciasteczko” przekazywane do procedury obsługi.

Każde przerwanie posiada swój numer w systemie, w przypadku GPIO nie musimy znać numeru przerwania generowanego przez używany pin GPIO, możemy do tego wykorzystać funkcję gpio_to_irq co też robimy.

Drugi parametr jest oczywisty, jest to funkcja, która zostanie wywołana po wystąpieniu tego przerwania.

Trzeci parametr to flagi informujące o właściwościach przerwania np. czy przerwanie może być współdzielone pomiędzy kilkoma procedurami obsługi. W naszym przypadku chcemy aby przerwanie było generowane tylko na zbocze narastające czyli tylko po wciśnięciu, nie chcemy aby przerwanie było obsługiwane podczas puszczania przycisku. Dlatego też przekazujemy flagę IRQF_TRIGGER_RISING.

Czwarty parametr to nazwa urządzenia, które generuje przerwanie, w zasadzie możesz tam wpisać cokolwiek.

Ostatni parametr jest nieco tajemniczy, jakieś ciasteczko, prawie jak w przypadku stron internetowych. Ten parametr służy do identyfikacji procedur obsługi przerwań w przypadku gdy chcemy je usunąć, a istnieje wiele procedur do obsługi danego przerwania, w zasadzie u nas by mogło ono wynosić NULL.

Jeszcze krótko o funkcji exit, w niej dochodzi linijka free_irq, funkcja ta służy do usunięcia danej procedury obsługi przerwań z systemu.

Teraz przejdźmy do samej procedury obsługi przerwania, wygląda ona następująco:

static irqreturn_t irq_handler(int irq, void *dev_id)
{
	led_state ^= 1;
	gpio_set_value(LED, led_state);
	return IRQ_HANDLED;
}

Musi ona posiadać dokładnie taką deklaracje tj. musi zwracać typ irqreturn_t, jest to po prostu enum, który może przybierać następujące wartości:

  • IRQ_NONE- przerwanie nieobsłużone
  • IRQ_HANDLED- przerwanie obsłużone
  • IRQ_WAKE_THREAD- przerwanie ma zostać obsłużone w wątku przerwania

Nas będą w najbliższej przyszłości interesować dwie pierwsze wartości.

Musi także przybierać takie dwa parametry- numer przerwania oraz nasze ciasteczko.

W samej funkcji dzieje się niewiele- zmieniamy stan diody poprzez zrobienie operacji XOR, a następnie zapisujemy nowy stan na wyjście pinu do którego jest podłączona dioda. Na koniec zwracamy wartość IRQ_HANDLED.

Makefile wygląda standardowo:

obj-m += 06_interrupt.o
all:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) modules
clean:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) 

Przebuduj moduł komendą odpowiednią dla swojej płytki:

# BBB
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
# RPi4
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KERNEL=kernel8

Testowanie sterownika

Jak już podłączyłeś diodę i przycisk tak jak to zostało opisane w poprzedniej sekcji prześlij sterownik na swoją płytkę np. za pomocą scp i załaduj go:

sudo insmod 06_interrupt.ko

Teraz możesz zmieniać stan diody poprzez wciskanie przycisku.

Ten wpis został opublikowany w kategorii Kurs pisania sterowników. Dodaj zakładkę do bezpośredniego odnośnika.

Dodaj komentarz

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