Interrupt APIs

The Interrupt class provides APIs to register interrupt handlers for GPIO and configure the interrupts. Interrupts can be configured to trigger on rising edge, falling edge, high level, low level, or any change in the input signal. A single interrupt object can manage multiple GPIO pins with separate callbacks for each GPIO, or separte Interrupt objects can be created (though that uses more memory and CPU as each Interrupt will has its own queue, ISR handler, and task.

Finally, the Interrupt class is also designed to be subclassed if desired to provide additional functionality, or it can of course be included as a member within another class. The Button class provides a very simple implementation of a subclass of Interrupt.

API Reference

Header File

Classes

class Interrupt : public espp::BaseComponent

A class to handle a GPIO interrupt.

This class uses the ESP-IDF GPIO interrupt handler to detect GPIO interrupts. It then calls the callback function with the event. It can handle multiple GPIO interrupts and call the appropriate callback for each GPIO interrupt.

The class uses a FreeRTOS queue to handle the events. The queue is created in the constructor and deleted in the destructor. The queue is used to wake up the task when an interrupt event occurs. The task then calls the appropriate callback for the interrupt. Since all the GPIO interrupts are handled by the same task, all the callbacks are called from the same task. This means that the callbacks should be fast and not block for long periods of time, otherwise the other interrupts will be delayed.

Regardless of the callback speed, some interrupts could still be missed if they happen too quickly. For this reason, the queue size can be set in the configuration (default is 10). If the queue is full, then the interrupt event will be missed. If you are expecting a lot of interrupts to happen quickly, then you should increase the queue size.

Another way to handle the situation where you have many interrupts and would like to separate out the processing by priority is to have different Interrupt objects for the different priority levels, and assign the interrupt pins/callbacks to the objects according to the priority levels that you want. This will ensure that interrupts at different priority levels / in different objects do not starve the each other, while ensuring that the interupts are still processed in an orderly fashion.

Interrupt Example

  static auto start = std::chrono::high_resolution_clock::now();

  auto callback = [&](const espp::Interrupt::Event &event) {
    auto now = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration<float>(now - start).count();
    logger.info("[Callback][{:.3f}] Interrupt: pin {} changed to active state: {}", elapsed,
                event.gpio_num, event.active);
  };

  espp::Interrupt::PinConfig io0 = {
      .gpio_num = GPIO_NUM_0,
      .callback = callback,
      .active_level = espp::Interrupt::ActiveLevel::LOW,
      .interrupt_type = espp::Interrupt::Type::ANY_EDGE,
      .pullup_enabled = true,
      .pulldown_enabled = false,
      // flexible filter requiring configuration (default is provided as 5us
      // threshold in 10us window), but other configurations can be manually
      // set as below
      .filter_type = espp::Interrupt::FilterType::FLEX_GLITCH_FILTER,
      .filter_config = {.window_width_ns = 10000, .window_threshold_ns = 5000},
  };
  espp::Interrupt::PinConfig io12 = {
      .gpio_num = GPIO_NUM_12,
      .callback = callback,
      .active_level = espp::Interrupt::ActiveLevel::LOW,
      .interrupt_type = espp::Interrupt::Type::ANY_EDGE,
      .pullup_enabled = true,
      .pulldown_enabled = false,
      // pre-configured 2 clock pulse width filter
      .filter_type = espp::Interrupt::FilterType::PIN_GLITCH_FILTER,
  };

  // make an interrupt for a single gpio
  {
    espp::Interrupt interrupt({
        .isr_core_id = 1,
        .interrupts = {io0},
        .task_config =
            {
                .name = "Interrupt task",
                .stack_size_bytes = 6192,
                .priority = 5,
            },
        .log_level = espp::Logger::Verbosity::DEBUG,
    });

    std::this_thread::sleep_for(5s);
  }

  // make multiple interrupts for multiple gpios
  {
    espp::Interrupt interrupt0({
        .interrupts = {io0},
        .task_config =
            {
                .name = "Interrupt task",
                .stack_size_bytes = 6192,
                .priority = 5,
            },
        .log_level = espp::Logger::Verbosity::DEBUG,
    });

    espp::Interrupt interrupt12({
        .interrupts = {io12},
        .task_config =
            {
                .name = "Interrupt 0 task",
                .stack_size_bytes = 6192,
                .priority = 5,
            },
        .log_level = espp::Logger::Verbosity::DEBUG,
    });

    std::this_thread::sleep_for(2s);

    // now lets read the instantaneous state of the interrupt pins
    auto is_0_active = interrupt0.is_active(io0);
    auto is_12_active = interrupt12.is_active(io12);
    logger.info("Instantaneous state of pin 0: {}", is_0_active);
    logger.info("Instantaneous state of pin 12: {}", is_12_active);

    std::this_thread::sleep_for(2s);
  }

  // make a single interrupt for multiple GPIOs
  // make an interrupt for a single gpio
  {
    // Register for interrupts on a few pins (GPIO_NUM_0, GPIO_NUM_12)
    espp::Interrupt interrupt({
        .interrupts = {io0},
        .task_config =
            {
                .name = "Interrupt task",
                .stack_size_bytes = 6192,
                .priority = 5,
            },
        .log_level = espp::Logger::Verbosity::DEBUG,
    });

    // use the add_interrupt method to add another interrupt
    interrupt.add_interrupt(io12);

    std::this_thread::sleep_for(2s);

    // now lets read the instantaneous state of the interrupt pins
    auto active_states = interrupt.get_active_states();
    logger.info("Instantaneous state of pins: {}", active_states);

    std::this_thread::sleep_for(2s);

    // print out the minimum number of spaces in the interrupt queue over the
    // last 2 seconds
    auto min_queue_size = interrupt.get_min_queue_size();
    logger.info("Minimum queue size over last 4 seconds: {}", min_queue_size);
  }

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
  esp_intr_dump(stdout);
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)

Subclassed by espp::Button

Public Types

enum class ActiveLevel

The active level of the GPIO.

Values:

enumerator LOW

Active low.

enumerator HIGH

Active high.

enum class Type

The type of interrupt to use for the GPIO.

Values:

enumerator ANY_EDGE

Interrupt on any edge.

enumerator RISING_EDGE

Interrupt on rising edge.

enumerator FALLING_EDGE

Interrupt on falling edge.

enumerator LOW_LEVEL

Interrupt on low level.

enumerator HIGH_LEVEL

Interrupt on high level.

typedef std::function<void(const Event&)> event_callback_fn

The callback for the event.

Public Functions

inline explicit Interrupt(const Config &config)

Constructor.

Parameters

config – The configuration for the interrupt

inline ~Interrupt()

Destructor.

inline size_t get_min_queue_size() const

Get the minimum number of free spaces in the queue.

This will return the minimum number of free spaces in the queue Over the lifetime of the object. This can be used to determine if the queue size is too small for the number of interrupts that are being received. It may also help indicate if the interrupt task priority is too low, preventing the queue from being serviced. Finally, it may also help to indicate if additional filtering may be needed on the interrupt line (either using the FilterType or with hardware filtering).

Returns

The minimum number of free spaces in the queue

inline void add_interrupt(const PinConfig &interrupt)

Add an interrupt to the interrupt handler.

Parameters

interrupt – The interrupt to add

inline bool is_active(const PinConfig &interrupt) const

Get the state of the interrupt.

This will check the raw logic level of the GPIO and return whether the interrupt is active or not according to the active level that was set in the configuration.

Parameters

interrupt – The interrupt to check

Returns

Whether the interrupt is active

inline std::vector<std::pair<int, bool>> get_active_states()

Get the state of all the interrupts.

Returns

A vector of the states of the interrupts as pairs of the GPIO number and whether the interrupt is active

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters

rate_limit – The rate limit to use for the logger

struct Config

The configuration for the interrupt.

Public Members

int isr_core_id = -1

The core to install the ISR service on. If -1, then the ISR service is installed on the core that this constructor is called on. If 0 or 1, then the ISR service is installed on the specified core. If the ISR service is already installed, then this function does nothing. If the core_id is invalid, then an error is logged and the ISR service is not installed.

std::vector<PinConfig> interrupts

The configuration for the interrupts.

size_t event_queue_size = 10

The size of the event queue.

Task::BaseConfig task_config

The configuration for the task.

espp::Logger::Verbosity log_level = espp::Logger::Verbosity::WARN

The log level for the interrupt.

struct Event

The event for the interrupt.

Public Members

uint8_t gpio_num

The GPIO number of the interrupt.

bool active

Whether the interrupt is active or not (based on the active level)

struct FilterConfig

The configuration for the filter.

This is used to configure the GPIO flex glitch filter The filter is used to filter out glitches on the GPIO whose pulses are shorter than window_threshold_ns within the window_width_ns sampling window.

Note

This is only supported on some chips (-C and -S series chips) and is only enabled if CONFIG_SOC_GPIO_FLEX_GLITCH_FILTER_NUM > 0.

Note

This filter config is only supported by the flex_glitch_filter. The pin_glitch_filter is not-configurable.

Public Members

uint32_t window_width_ns{10000}

The width of the sampling window in nanoseconds. Default is 10us.

uint32_t window_threshold_ns = {5000}

The threshold for the sampling window in nanoseconds. If the width of the pulse is less than this, it is filtered out. Default is 5us

struct PinConfig

The configuration for an interrupt on a GPIO.

This is used to configure the GPIO interrupt

Public Members

int gpio_num

GPIO number to for this interrupt.

event_callback_fn callback

Callback for the interrupt event.

ActiveLevel active_level

Active level of the GPIO.

Type interrupt_type = Type::ANY_EDGE

Interrupt type to use for the GPIO.

bool pullup_enabled = false

Whether to enable the pullup resistor.

bool pulldown_enabled = false

Whether to enable the pulldown resistor.

FilterType filter_type = FilterType::NONE

The type of filter to use. If set to FLEX_GLITCH_FILTER, the filter_config should be set.

FilterConfig filter_config = {}

The configuration for the filter. This is only used if filter_type is set to FLEX_GLITCH_FILTER