From 5746091abb32400dece6759113436f169a02b831 Mon Sep 17 00:00:00 2001 From: cocasema Date: Thu, 14 Apr 2022 12:22:24 -0700 Subject: [PATCH] [rp2040] Implement IRQ handlers for GPIO and QSPI Based on discussion #847 --- README.md | 22 +++ examples/rp_pico/interrupt/main.cpp | 40 ++++++ examples/rp_pico/interrupt/project.xml | 11 ++ .../platform/extint/rp/int_controller.hpp | 73 ++++++++++ .../platform/extint/rp/int_handler.cpp.in | 94 +++++++++++++ .../platform/extint/rp/int_handler.hpp.in | 133 ++++++++++++++++++ src/modm/platform/extint/rp/module.lb | 58 ++++++++ src/modm/platform/extint/rp/module.md | 45 ++++++ src/modm/platform/gpio/rp/base.hpp.in | 59 ++++++++ src/modm/platform/gpio/rp/module.lb | 13 ++ 10 files changed, 548 insertions(+) create mode 100644 examples/rp_pico/interrupt/main.cpp create mode 100644 examples/rp_pico/interrupt/project.xml create mode 100644 src/modm/platform/extint/rp/int_controller.hpp create mode 100644 src/modm/platform/extint/rp/int_handler.cpp.in create mode 100644 src/modm/platform/extint/rp/int_handler.hpp.in create mode 100644 src/modm/platform/extint/rp/module.lb create mode 100644 src/modm/platform/extint/rp/module.md diff --git a/README.md b/README.md index d1913692a0..fe258d2518 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,28 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ +IRQ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✕ +✅ +✕ +✕ +✕ + Random Generator ✕ ✕ diff --git a/examples/rp_pico/interrupt/main.cpp b/examples/rp_pico/interrupt/main.cpp new file mode 100644 index 0000000000..5f9b0e45b1 --- /dev/null +++ b/examples/rp_pico/interrupt/main.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022, Nikolay Semenov + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +int +main() +{ + Board::initialize(); + + using Led = Board::LedGreen; + Led::setOutput(); + Led::set(); + + GpioIntHandler::enable(); + + // Powers on the LED on the low->high transition, and off on high->low. + GpioInput0::setInput(); + GpioIntHandler::connect(Gpio::InputTrigger::BothEdges, + [](Gpio::InputTrigger triggers) { + Led::set(!!(triggers & Gpio::InputTrigger::RisingEdge)); + }); + + // Toggles LED each time gpio input is at the high level. + GpioInput1::setInput(Gpio::InputType::PullDown); + GpioIntHandler::connect(Gpio::InputTrigger::HighLevel, + [](Gpio::InputTrigger) { Led::toggle(); }); + + while (true) {} + + return 0; +} diff --git a/examples/rp_pico/interrupt/project.xml b/examples/rp_pico/interrupt/project.xml new file mode 100644 index 0000000000..f41621481e --- /dev/null +++ b/examples/rp_pico/interrupt/project.xml @@ -0,0 +1,11 @@ + + modm:rp-pico + + + + + modm:platform:gpio + modm:platform:irq + modm:build:scons + + diff --git a/src/modm/platform/extint/rp/int_controller.hpp b/src/modm/platform/extint/rp/int_controller.hpp new file mode 100644 index 0000000000..0d85eab6c2 --- /dev/null +++ b/src/modm/platform/extint/rp/int_controller.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022, Nikolay Semenov + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +namespace modm::platform +{ + +/** + * Priority level of 0-192 in steps of 64 for each interrupt. + * A higher level corresponds to a lower priority, + * so level 0 is the highest programmable interrupt priority. + * ... + * The processor implements only bits[7:6] of each field, bits [5:0] read as zero and ignore writes. + * This means writing 255 to a priority register saves value 192 to the register. + * + * https://developer.arm.com/documentation/dui0662/b/Cortex-M0--Peripherals/Nested-Vectored-Interrupt-Controller + * https://developer.arm.com/documentation/dui0662/b/Cortex-M0--Peripherals/Nested-Vectored-Interrupt-Controller/Interrupt-Priority-Registers + * + * @ingroup modm_platform_irq + */ +enum IntPriority : uint8_t +{ + Highest = 0x00, + Default = 0x80, + Lowest = 0xff, +}; + +/** + * Interrupt Controller + * + * @ingroup modm_platform_irq + */ +class IntController +{ +public: + static void + enable(IRQn_Type type) + { + NVIC_ClearPendingIRQ(type); + NVIC_EnableIRQ(type); + } + + static void + disable(IRQn_Type type) + { + NVIC_DisableIRQ(type); + } + + static void + setPriority(IRQn_Type type, IntPriority priority) + { + NVIC_SetPriority(type, priority); + } + + static IntPriority + getPriority(IRQn_Type type) + { + return static_cast(NVIC_GetPriority(type)); + } +}; + +} // namespace modm::platform \ No newline at end of file diff --git a/src/modm/platform/extint/rp/int_handler.cpp.in b/src/modm/platform/extint/rp/int_handler.cpp.in new file mode 100644 index 0000000000..fddaf6e165 --- /dev/null +++ b/src/modm/platform/extint/rp/int_handler.cpp.in @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022, Nikolay Semenov + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +namespace modm::platform +{ + +%% if with_gpio +template<> +void +IntHandler::irqHandler() +{ + using PortRegs = Gpio::PortRegs; + + static_assert(0b1111u == static_cast(Gpio::InputTrigger::All)); + + // TODO: check if multicore enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? iobank0_hw->proc1_irq_ctrl : iobank0_hw->proc0_irq_ctrl; + + for (size_t group = 0; group < Lines / 8; ++group) + { + if (uint32_t int_status = proc_irq_ctrl.ints[group]) + { + for (uint8_t pin = group * 8; int_status; ++pin, int_status >>= 4) + { + if (uint32_t triggers = int_status & 0b1111u) + { + PortRegs::acknowledge_irq(pin, static_cast(triggers)); + if (auto& handler = handlers[pin]) + { + handler(static_cast(triggers)); + } + } + } + } + } +} +%%endif + +%% if with_qspi +template<> +void +IntHandler::irqHandler() +{ + using PortRegs = Gpio::PortRegs; + + static_assert(Lines <= 8); + + // TODO: check if multicore enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? ioqspi_hw->proc1_qspi_ctrl : ioqspi_hw->proc0_qspi_ctrl; + + uint32_t int_status = proc_irq_ctrl.ints; + + for (uint8_t pin = 0; int_status; ++pin, int_status >>= 4) + { + if (uint32_t triggers = int_status & 0b1111u) + { + PortRegs::acknowledge_irq(pin, static_cast(triggers)); + if (auto& handler = handlers[pin]) + { + handler(static_cast(triggers)); + } + } + } +} +%%endif + +template +IntHandler::Handler IntHandler::handlers[IntHandler::Lines] modm_fastdata; + +%% if with_gpio +MODM_ISR(IO_IRQ_BANK0) +{ + IntHandler::irqHandler(); +} +%%endif + +%% if with_qspi +MODM_ISR(IO_IRQ_QSPI) +{ + IntHandler::irqHandler(); +} +%%endif + +} // namespace modm::platform \ No newline at end of file diff --git a/src/modm/platform/extint/rp/int_handler.hpp.in b/src/modm/platform/extint/rp/int_handler.hpp.in new file mode 100644 index 0000000000..ef44fdf374 --- /dev/null +++ b/src/modm/platform/extint/rp/int_handler.hpp.in @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, Nikolay Semenov + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +namespace modm::platform +{ +%% if with_gpio +MODM_ISR_DECL(IO_IRQ_BANK0); +%%endif +%% if with_qspi +MODM_ISR_DECL(IO_IRQ_QSPI); +%%endif + +/** + * Interrupt Handler + * + * @ingroup modm_platform_irq + */ +template +class IntHandler +{ +public: + static constexpr size_t Lines = NLines; + using Handler = + modm::inplace_function; + +public: + static void + enable(IntPriority priority = IntPriority::Default) + { + if (priority != IntController::getPriority(Type)) + { + IntController::setPriority(Type, priority); + } + IntController::enable(Type); + } + + static void + disable() + { + IntController::disable(Type); + } + + template + static void + connect(Gpio::InputTrigger triggers, Handler&& handler) + { + static_assert(Pin::port == gpioPort()); + + disableInterrupts(Gpio::InputTrigger::All); + acknowledgeInterrupts(Gpio::InputTrigger::All); + + handlers[Pin::pin] = handler; + + enableInterrupts(triggers); + } + + template + static void + disconnect() + { + static_assert(Pin::port == gpioPort()); + + disableInterrupts(Gpio::InputTrigger::All); + handlers[Pin::pin] = nullptr; + } + +private: + template + static void + enableInterrupts(Gpio::InputTrigger triggers) + { + Gpio::PortRegs::enable_irq(Pin::pin, triggers); + } + + template + static void + disableInterrupts(Gpio::InputTrigger triggers) + { + Gpio::PortRegs::disable_irq(Pin::pin, triggers); + } + + template + static void + acknowledgeInterrupts(Gpio::InputTrigger triggers) + { + Gpio::PortRegs::acknowledge_irq(Pin::pin, triggers); + } + + static void + irqHandler(); + friend void MODM_ISR_NAME(IO_IRQ_BANK0)(); + friend void MODM_ISR_NAME(IO_IRQ_QSPI)(); + + // In the current implementation we do not allow handlers + // for the same line (pin) for more than a single core. + static Handler handlers[Lines]; + + static constexpr Gpio::Port + gpioPort() + { +%% if with_gpio + if constexpr (Type == IO_IRQ_BANK0_IRQn) { return Gpio::Port::Bank0; } +%%endif +%% if with_qspi + if constexpr (Type == IO_IRQ_QSPI_IRQn) { return Gpio::Port::Qspi; } +%%endif + return static_cast(-1); + } +}; + +%% if with_gpio +using GpioIntHandler = IntHandler; +%%endif +%% if with_qspi +using QspiIntHandler = IntHandler; +%%endif + +} // namespace modm::platform \ No newline at end of file diff --git a/src/modm/platform/extint/rp/module.lb b/src/modm/platform/extint/rp/module.lb new file mode 100644 index 0000000000..f81ad66590 --- /dev/null +++ b/src/modm/platform/extint/rp/module.lb @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, Nikolay Semenov +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:irq" + module.description = FileReader("module.md") + +def prepare(module, options): + module.depends( + ":cmsis:device", + ":platform:gpio") + module.add_option( + BooleanOption( + name="gpio", + description="Enable IRQ support for GPIO", + default=True)) + module.add_option( + BooleanOption( + name="qspi", + description="Enable IRQ support for QSPI", + default=False)) + module.add_option( + StringOption( + name="handler.storage", + description="Size of storage for the handler's implementation", + default="sizeof(void*)")) + + return options[":target"].identifier.platform == "rp" + +def validate(env): + if not any([env.get("gpio"), env.has_module("qspi")]): + raise ValidateException("At least one IRQ type must be enabled!") + +def build(env): + multicore_enabled = env.has_module(":platform:multicore") + + with_gpio = env.get("gpio", True) + with_qspi = env.get("qspi", False) + handler_storage = env.get("handler.storage", "sizeof(void*)") + + env.substitutions = { + "multicore_enabled": multicore_enabled, + "with_gpio": with_gpio, + "with_qspi": with_qspi, + "handler_storage": handler_storage, + } + env.outbasepath = "modm/src/modm/platform/irq" + env.copy("int_controller.hpp") + env.template("int_handler.hpp.in") + env.template("int_handler.cpp.in") diff --git a/src/modm/platform/extint/rp/module.md b/src/modm/platform/extint/rp/module.md new file mode 100644 index 0000000000..3636615a2c --- /dev/null +++ b/src/modm/platform/extint/rp/module.md @@ -0,0 +1,45 @@ +# External Interrupt Handler + +This driver provides an API for configuring all IRQ lines via register access. +Note that you need to pass a mask, which allows you to configure multiple IRQ +lines at once. + +```cpp +GpioIntHandler::enable(); + +// Powers on the LED on the low->high transition, and off on high->low. +GpioInput0::setInput(); +GpioIntHandler::connect(Gpio::InputTrigger::BothEdges, + [](Gpio::InputTrigger triggers) { + Led::set(!!(triggers & Gpio::InputTrigger::RisingEdge)); + }); + +// Toggles LED each time gpio input is at the high level. +GpioInput1::setInput(Gpio::InputType::PullDown); +GpioIntHandler::connect(Gpio::InputTrigger::HighLevel, + [](Gpio::InputTrigger) { Led::toggle(); }); +``` + +## Multicore mode + +Each core can register callbacks in the same IntHandler, +but for the different pins (current implementation's constraint). + +Also, enable/disable and connect/disconnect calls +affect the NVIC of the executing core only. + +## Callbacks + +The callback is implemented using `modm::inplace_function`, therefore uses no +heap, but has a fixed storage size of `sizeof(void*)` by default. +You can increase this storage size in your `project.xml` options: + +```xml + + + + + + + +``` \ No newline at end of file diff --git a/src/modm/platform/gpio/rp/base.hpp.in b/src/modm/platform/gpio/rp/base.hpp.in index d2a38ebda2..a0614197d0 100644 --- a/src/modm/platform/gpio/rp/base.hpp.in +++ b/src/modm/platform/gpio/rp/base.hpp.in @@ -35,6 +35,19 @@ struct Gpio PullDown = 0x2, ///< pull-down on input }; + enum class + InputTrigger : uint32_t + { + None = 0x00u, + LowLevel = 0x01u, + HighLevel = 0x02u, + BothLevels = LowLevel | HighLevel, + FallingEdge = 0x04u, + RisingEdge = 0x08u, + BothEdges = FallingEdge | RisingEdge, + All = BothLevels | BothEdges, + }; + enum class OutputType { @@ -47,6 +60,7 @@ struct Gpio Slow = 0, Fast = 1, }; + enum class DriveStrength { @@ -55,6 +69,7 @@ struct Gpio mA_8 = 2, mA_12 = 3, }; + enum class SlewRate : uint8_t { @@ -133,8 +148,52 @@ struct Gpio::PortRegs PADS_BANK0_GPIO0_SLEWFAST_BITS ); } + static void enable_irq(uint8_t pin, InputTrigger triggers) + { + uint32_t value = static_cast(triggers) << 4 * {{intreg_pin_mask_shift[port]}}; +%% if multicore_enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? io{{port | lower}}_hw->proc1_{{intreg_ctrl_names[port]}} : io{{port | lower}}_hw->proc0_{{intreg_ctrl_names[port]}}; +%% else + auto& proc_irq_ctrl = io{{port | lower}}_hw->proc0_{{intreg_ctrl_names[port]}}; +%% endif + hw_set_bits(&proc_irq_ctrl.inte{{intreg_pin_access[port]}}, value); + } + static void disable_irq(uint8_t pin, InputTrigger triggers) + { + uint32_t value = static_cast(triggers) << 4 * {{intreg_pin_mask_shift[port]}}; +%% if multicore_enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? io{{port | lower}}_hw->proc1_{{intreg_ctrl_names[port]}} : io{{port | lower}}_hw->proc0_{{intreg_ctrl_names[port]}}; +%% else + auto& proc_irq_ctrl = io{{port | lower}}_hw->proc0_{{intreg_ctrl_names[port]}}; +%% endif + hw_clear_bits(&proc_irq_ctrl.inte{{intreg_pin_access[port]}}, value); + } + static void acknowledge_irq(uint8_t pin, InputTrigger triggers) + { + uint32_t value = static_cast(triggers) << 4 * {{intreg_pin_mask_shift[port]}}; + io{{port | lower}}_hw->intr{{intreg_pin_access[port]}} = value; + } }; + %% endfor /// @endcond +inline constexpr Gpio::InputTrigger operator|(Gpio::InputTrigger a, Gpio::InputTrigger b) +{ + using enum_ut = std::underlying_type_t; + return static_cast(static_cast(a) | static_cast(b)); +} + +inline constexpr Gpio::InputTrigger operator&(Gpio::InputTrigger a, Gpio::InputTrigger b) +{ + using enum_ut = std::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); +} + +inline constexpr bool operator!(Gpio::InputTrigger it) +{ + using enum_ut = std::underlying_type_t; + return !static_cast(it); +} + } // namespace modm::platform diff --git a/src/modm/platform/gpio/rp/module.lb b/src/modm/platform/gpio/rp/module.lb index 6ae1fa611b..823d6c3771 100644 --- a/src/modm/platform/gpio/rp/module.lb +++ b/src/modm/platform/gpio/rp/module.lb @@ -54,6 +54,18 @@ def build(env): "Bank0": "bank0", "Qspi": "_qspi" }, + "intreg_ctrl_names": { + "Bank0": "irq_ctrl", + "Qspi": "qspi_ctrl" + }, + "intreg_pin_access": { + "Bank0": "[pin / 8]", + "Qspi": "" + }, + "intreg_pin_mask_shift": { + "Bank0": "(pin % 8)", + "Qspi": "pin" + }, "port_width": 32 } subs["ports"] = OrderedDict([(k, i) for i, k in enumerate([p["name"].capitalize() for p in subs["ranges"]])]) @@ -86,6 +98,7 @@ def build(env): subs["target"] = device.identifier subs["all_signals"] = all_signals subs["gpios"] = [subs[gpio["name"].capitalize()] for gpio in driver["gpio"]] + subs["multicore_enabled"] = env.has_module(":platform:multicore") env.substitutions = subs env.outbasepath = "modm/src/modm/platform/gpio"