Работа с кнопкой уже была продемонстрирована в публикации Управляем контактами GPIO из C# .NET 5 в Linux, но без детального рассмотрения прерываний (interrupt). Поэтому на этом моменте остановимся подробнее. Рассмотрим как работает прерывание, какие бывают триггеры срабатывания, и сделаем пример на C# используя библиотеку Libgpiod. Для примера будем использовать туже самую кнопку.
Оглавление
- Постановка задачи
- Прерывания (interrupt)
- Принципиальная схема подключения светодиода (LED) и кнопки
- Подготовка устройства
- Подготовка среды запуска (ПК)
- Создание обработчика прерываний от кнопки
- Использование драйвера Button
- Литература
Постановка задачи
Реализовать программу обработки прерываний (interrupt) от кнопки на C# .NET IoT используя библиотеку Libgpiod. При нажатии на кнопку будет включаться светодиод, повторное нажатие выключит светодиод. Кнопку будем подключать к плате Banana Pi BPI-M64.
- Плата Banana Pi BPI-M64 установлена версия Armbian_21.02.1_Bananapim64_bionic_current_5.10.12_minimal.img.xz, основана на Ubuntu 18.04.5 LTS (Bionic Beaver), ядро Linux 5.10.12. uname: Linux bananapim64 5.10.12-sunxi64 #21.02.1 SMP Wed Feb 3 20:42:58 CET 2021 aarch64 aarch64 aarch64 GNU/Linux.
Прерывания (interrupt)
Прерывание (interrupt) — сигнал, заставляющий компьютер менять обычный порядок выполнения команд процессором. Возникновение подобных сигналов обусловлено такими событиями, как:
- завершение операций ввода-вывода;
- истечение заранее заданного интервала времени;
- сбой в работе аппаратного устройства и др.
Обработчик прерываний – программа обработки прерывания, являющаяся частью ОС, предназначенная для выполнения ответных действий на условие, вызвавшее прерывание.
Самым наглядным примером прерывания является обработка событий нажатий кнопок на клавиатуре. В не зависимости, какие фоновые процессы выполняет ОС, нажатия на кнопки должны быть всегда обработаны. Если потребуется в Linux немедленно завершить текущую программу, то пользователь нажмет комбинацию Ctrl+V и произойдет прекращение работы программы в терминале. Это достигается путем цепочки обработки прерываний от аппаратных до программных. Аппаратные прерывания (в Linux называются IRQ, сокращенно от Interrupt ReQuests — Запросы на Прерывание) — события, которые исходят от внешних источников (например, периферийных устройств) и могут произойти в любой произвольный момент (принцип асинхронности): сигнал от таймера, сетевой карты или дискового накопителя, нажатие клавиш клавиатуры, движение мыши. Обработчиком аппаратного прерывания является драйвер устройства.
Далее, в ОС срабатывает программное прерывание. Программное прерывание — инициируются исполнением специальной инструкции в коде программы. Программные прерывания, как правило, используются для обращения к функциям встроенного программного обеспечения (firmware), драйверов и операционной системы. Вызываются искусственно с помощью соответствующей команды из программы (int), предназначены для выполнения некоторых действий операционной системы, являются синхронными.
При работе с контактами GPIO, можно считывать состояние «0» или «1», где «0» — цепь подтянута к земле (GND), «1» — цепь подтянута к питанию (VCC). При работе с кнопкой регистрируются следующие триггеры:
- нажатие на кнопку;
- отпускание кнопки из состояния «нажата».
При нажатии на кнопку происходит изменения состояния с «0» на «1», когда отпускаем наоборот.
В библиотеке libgpiod определяется два триггера RISING и FALLING:
- RISING — повышение, изменение напряжения с 0V до 3.3V, кнопка нажата и удерживается состояние;
- FALLING — понижение, изменение напряжения с 3.3V до 0V, происходит отпускание кнопки, и кнопка переходит в состояние «не нажата».
Данные события берутся из ядра Linux. По сути, библиотека libgpiod обрабатывает события, поступающие из ОС. В ядре Linux например версии v5.15.8 файл /include/dt-bindings/interrupt-controller/irq.h определяются следующие константы:
/* SPDX-License-Identifier: GPL-2.0 OR MIT */ /* * This header provides constants for most IRQ bindings. * * Most IRQ bindings include a flags cell as part of the IRQ specifier. * In most cases, the format of the flags cell uses the standard values * defined in this header. */ #ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_H #define _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_H #define IRQ_TYPE_NONE 0 #define IRQ_TYPE_EDGE_RISING 1 #define IRQ_TYPE_EDGE_FALLING 2 #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) #define IRQ_TYPE_LEVEL_HIGH 4 #define IRQ_TYPE_LEVEL_LOW 8 #endif
Помимо событий RISING и FALLING существую триггеры:
- IRQ_TYPE_LEVEL_HIGH — срабатывает только когда состояние выставляется в High;
- IRQ_TYPE_LEVEL_LOW — срабатывает только когда состояние выставляется в Low.
Библиотека libgpiod подписывается только на события IRQ_TYPE_EDGE_BOTH, т.е. IRQ_TYPE_EDGE_FALLING и IRQ_TYPE_EDGE_RISING. Данные константы для .NET кода не потребуются, но они используются для создания конфигурации устройств в дерево устройств (Device Tree), более подробно в публикации Работа с GPIO на примере Banana Pi BPI-M64. Часть 2. Device Tree overlays. Данный пример приведен для понимания как это работает на уровне ядра Linux.
Необходимо обратить внимание, что не каждый контакт (pin) процессора поддерживает прерывание. Об поддержки прерываний можно узнать из datasheet к процессору. Кнопку будем подключать к контакту №35 (40-контактный разъем совместимый с Raspberry Pi 3), название контакта PB6, номер линии — 38. При просмотре документации к процессору Allwinner_A64_Datasheet_V1.1.pdf в разделе 4.2. GPIO Multiplexing Functions указаны функции контактов. Среди перечисленных функций есть функции с префиксом PB_EINT*, которые означают поддержку аппаратного прерывания.
Контакт PB6 на процессоре Allwinner A64 поддерживает аппаратное прерывание
Дополнительно, наглядно посмотреть наличие поддержки аппаратных прерываний для контактов можно в схеме. Теперь подключим кнопку и напишем .NET код обработчика событий кнопки.
Принципиальная схема подключения светодиода (LED) и кнопки
Схема подключения светодиода (LED) и кнопки к 40-контактному разъему совместимый с Raspberry Pi 3
Схема назначения контактов, к которым подключается светодиод (LED) и кнопка
Подготовка устройства
На плате Banana Pi BPI-M64 должна быть установлена платформа .NET Runtime 5.0 и отладчик vsdbg, как это сделать указано в публикации Удаленная отладка приложения на .NET 5.0 в Visual Studio Code для ARM на примере Banana Pi BPI-M64 и Cubietruck (Armbian, Linux). А также библиотека Libgpiod.
Подготовка среды запуска (ПК)
Отладку будем запускать из среды Visual Studio Code на ПК под ОС Windows 7. Как установить и настроить среду в публикации Создание первого приложения на .NET 5.0 в Visual Studio Code для ARM.
Создание обработчика прерываний от кнопки
Создадим .NET 5.0 консольное приложение — dotnet-led-button. Добавим Nuget-пакет System.Device.Gpio. Программа основана на примере Push button.
Разместим в Program.cs следующий программный код, который по нажатию на кнопку включит светодиод, последующее нажатие выключит светодиод, и так до бесконечности:
class Program { private const int GPIOCHIP = 1; private const int LED_PIN = 36; private const int BUTTON_PIN = 38; private static PinValue ledPinValue = PinValue.Low; private static GpioController controller; private static int exitCode=0; static void Main(string[] args) { var drvGpio = new LibGpiodDriver(GPIOCHIP); controller = new GpioController(PinNumberingScheme.Logical, drvGpio); //set value if(!controller.IsPinOpen(LED_PIN)&&!controller.IsPinOpen(BUTTON_PIN)) { controller.OpenPin(LED_PIN,PinMode.Output); controller.OpenPin(BUTTON_PIN,PinMode.Input); } else { Console.WriteLine("LED_PIN or BUTTON_PIN is busy"); exitCode=-1; return; } controller.Write(LED_PIN,ledPinValue); //LED OFF // Console.WriteLine("CTRL+C to interrupt the read operation:"); controller.RegisterCallbackForPinValueChangedEvent(BUTTON_PIN,PinEventTypes.Rising,(o, e) => { ledPinValue=!ledPinValue; controller.Write(LED_PIN,ledPinValue); Console.WriteLine($"Press button, LED={ledPinValue}"); }); //Console string cki; while (true) { ... } Environment.Exit(exitCode); } }
Описание кода:
- GpioController — класс контроллера для управления контактами GPIO;
- LibGpiodDriver(GPIOCHIP) — драйвер обертки библиотеки Libgpiod, в качестве аргумента указываем номер gpiochip;
- GpioController(PinNumberingScheme.Logical, drvGpio) — инициализация контроллера, PinNumberingScheme.Logical — формат указания контактов. Есть два варианта, по названию контакта или по его номеру. Но т.к. названия контактов не заданы, то обращение будет только по номеру;
- controller.OpenPin — открытие контакта и задание ему режима работы, PinMode.Input на ввод, PinMode.Output на вывод (сигнал поступает от кнопки);
- controller.Write(LED_PIN, PinValue.High | PinValue.Low) — выставление контакту LED_PIN значение PinValue.High или PinValue.Low;
- RegisterCallbackForPinValueChangedEvent — регистрация Callback на контакт BUTTON_PIN, будет срабатывать при нажатие на кнопку — Rising. Так же доступно срабатывание на событие отпускание кнопки.
Запуск приложения в Visual Studio Code
Отладка приложения dotnet-led-button
Приложение dotnet-led-button на GitHub — .NET IoT Samples.
Использование драйвера Button
Разрабатывать приложения на .NET можно не только для платформы Linux, но и для микроконтроллеров. Для микроконтроллеров используется платформа nanoFramework, API которой насколько это возможно совместимо с .NET IoT. Для проекта на .NET IoT был взят драйвер/класс кнопки Button (nanoFramework.IoT.Device), позволяющий подписываться на события одиночного и двойного клика, с фильтрацией дребезга кнопки. Классы драйвера Button в проект dotnet-iot-button-from-nanoframework были перенесены, без какого либо изменения, располагаются по пути /libs/Button/.
В Program.cs добавим следующий код:
Файл Program.cs.
//for Linux const int GPIOCHIP = 1; const int BUTTON_PIN = 38; GpioController controller; var drvGpio = new LibGpiodDriver(GPIOCHIP); controller = new GpioController(PinNumberingScheme.Logical, drvGpio); // Initialize a new button with the corresponding button pin var timespanDoublePress = TimeSpan.FromMilliseconds(300); var timespanHolding = TimeSpan.FromMilliseconds(100); var button = new GpioButton(buttonPin: BUTTON_PIN, doublePress: timespanDoublePress, holding: timespanHolding, gpio: controller,false,PinMode.Input); Debug.WriteLine("Button is initialized, starting to read state"); // Enable or disable holding or doublepress events button.IsDoublePressEnabled = true; button.IsHoldingEnabled = true; // Write to debug if the button is down button.ButtonDown += (sender, e) => { Debug.WriteLine($"buttondown IsPressed={button.IsPressed}"); }; ...
Запуск приложения в Visual Studio Code
Отладка приложения dotnet-iot-button-from-nanoframework с драйвером Button из nanoFramework
Приложение dotnet-iot-button-from-nanoframework на GitHub — .NET IoT Samples.
Литература
- Управляем контактами GPIO из C# .NET 5 в Linux на одноплатном компьютере Banana Pi M64 (ARM64) и Cubietruck (ARM32) — DevDotNet.ORG
- Работа с GPIO в Linux. Часть 6. Библиотека Libgpiod — DevDotNet.ORG
- Interrupts — The Linux Kernel 5.16.0-rc5
- Interrupts — The Linux Kernel 5.10.14
- How the Linux kernel handles interrupts — Opensource.com
- Interrupts Example Program in Linux Kernel – Linux Device Driver Tutorial Part 13 embetronicx.com
- /include/dt-bindings/interrupt-controller/irq.h — Linux kernel 5.15.8 bootlin.com