.NET IoT. Часть 5. Обработка прерываний (interrupt) на примере событий кнопки

Работа с кнопкой уже была продемонстрирована в публикации Управляем контактами GPIO из C# .NET 5 в Linux, но без детального рассмотрения прерываний (interrupt). Поэтому на этом моменте остановимся подробнее. Рассмотрим как работает прерывание, какие бывают триггеры срабатывания, и сделаем пример на C# используя библиотеку Libgpiod. Для примера будем использовать туже самую кнопку.

Оглавление

  1. Постановка задачи
  2. Прерывания (interrupt)
  3. Принципиальная схема подключения светодиода (LED) и кнопки
  4. Подготовка устройства
  5. Подготовка среды запуска (ПК)
  6. Создание обработчика прерываний от кнопки
  7. Использование драйвера Button
  8. Литература

Постановка задачи

Реализовать программу обработки прерываний (interrupt) от кнопки на C# .NET IoT используя библиотеку Libgpiod. При нажатии на кнопку будет включаться светодиод, повторное нажатие выключит светодиод. Кнопку будем подключать к плате Banana Pi BPI-M64.

Прерывания (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*, которые означают поддержку аппаратного прерывания.

dotnet iot button
Контакт PB6 на процессоре Allwinner A64 поддерживает аппаратное прерывание

Дополнительно, наглядно посмотреть наличие поддержки аппаратных прерываний для контактов можно в схеме. Теперь подключим кнопку и напишем .NET код обработчика событий кнопки.

Принципиальная схема подключения светодиода (LED) и кнопки

Схема подключения светодиода (LED) и кнопки к 40-контактному разъему совместимый с Raspberry Pi 3
dotnet iot button

Схема назначения контактов, к которым подключается светодиод (LED) и кнопка
dotnet iot button

Подготовка устройства

На плате 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 iot button
Отладка приложения 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
Отладка приложения dotnet-iot-button-from-nanoframework с драйвером Button из nanoFramework

Приложение  dotnet-iot-button-from-nanoframework на GitHub — .NET IoT Samples.

Литература

Вам также может понравиться

About the Author: Anton

Programistik