Przyciski
Do tej pory w naszym kursie omówiliśmy interfejsy wyjściowe, takie jak klasyczny LED oraz rozbudowany moduł logowania. Zanim przejdziemy do bardziej zaawansowanych zagadnień, uruchomimy prosty interfejs wejściowy. Będzie to zestaw 4 przycisków zamontowanych na płytce nRF5340 DK, z których każdy jest podłączony do osobnego pinu.
Zaczynamy od utworzenia nowego, pustego projektu i sprawdzenia konfiguracji przycisków w devicetree (listing 1). Konfiguracja składa się z listy przycisków, umieszczonych w bloku kompatybilnym ze wskazaniem „gpio-keys”. Każdy przycisk ma swoje pole gpios, w którym określamy pin oraz dodatkowe parametry.
buttons {
compatible = "gpio-keys";
button0: button_0 {
gpios = <&gpio0 23 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button 1";
zephyr,code = <INPUT_KEY_0>;
};
button1: button_1 {
gpios = <&gpio0 24 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button 2";
zephyr,code = <INPUT_KEY_1>;
};
button2: button_2 {
gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button 3";
zephyr,code = <INPUT_KEY_2>;
};
button3: button_3 {
gpios = <&gpio0 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button 4";
zephyr,code = <INPUT_KEY_3>;
};
};
Listing 1. Konfiguracja przycisków w devicetree
Następnie wypełniamy plik main.c zgodnie z listingiem 2. Kod do obsługi przycisków korzysta ze znanego nam już modułu gpio. Nowością jest sposób konfiguracji przerwań oraz funkcja callback, która wykonuje się przy każdym naciśnięciu przycisku.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
static const struct gpio_dt_spec buttons[] = {
GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button1), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button2), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button3), gpios)
};
static struct gpio_callback cb_data;
static void button_callback(const struct device *dev,
struct gpio_callback *cb, uint32_t pins)
{
LOG_INF("Pin mask 0x%08x", pins);
}
int main(void) {
gpio_init_callback(&cb_data, button_callback,
BIT(buttons[0].pin) | BIT(buttons[1].pin) |
BIT(buttons[2].pin) | BIT(buttons[3].pin));
for (int i = 0; i < ARRAY_SIZE(buttons); ++i) {
gpio_pin_configure_dt(&buttons[i], GPIO_INPUT);
gpio_pin_interrupt_configure_dt(&buttons[i],
GPIO_INT_EDGE_FALLING);
gpio_add_callback(buttons[i].port, &cb_data);
}
int counter = 0;
while (1) {
LOG_INF("Tick %d", counter++);
k_msleep(1000);
}
return 0;
}
Listing 2. Plik main.c
Po skompilowaniu i wgraniu aplikacji na płytkę możemy zaobserwować w konsoli logów, że każde naciśnięcie przycisku generuje log informacyjny z maską bitową identyfikującą przycisk.
Warto wspomnieć, ile pracy wykonuje za nas Zephyr. Dzięki odpowiedniej konfiguracji opadające zbocze na dowolnym z pinów przypisanych do przycisków wywołuje przerwanie, które – obsługiwane przez Zephyra – uruchamia naszą funkcję button_callback, podając pin wywołujący przerwanie jako parametr pins. Nie musimy konfigurować żadnych rejestrów przerwań. Pamiętajmy jednak, że funkcja button_callback jest wywoływana bezpośrednio z przerwania, więc nie powinna zajmować zbyt wiele czasu. W naszym przypadku zlecamy tylko wysłanie loga wątkowi logowania poprzez makro LOG_INF i kończymy pracę w kontekście przerwania.
Podczas debugowania programu proponujemy ustawić breakpoint w funkcji button_callback. Po zatrzymaniu programu zwróć uwagę na stos wywołań (call stack).