From 599e0bab0e5faa6d8fe348cf41cefc9f4a9784d4 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 | 2 +- examples/rp_pico/interrupt/main.cpp | 38 +++++ examples/rp_pico/interrupt/project.xml | 10 ++ .../platform/extint/rp/int_handler.cpp.in | 98 ++++++++++++ .../platform/extint/rp/int_handler.hpp.in | 145 ++++++++++++++++++ src/modm/platform/extint/rp/int_priority.hpp | 39 +++++ src/modm/platform/extint/rp/module.lb | 67 ++++++++ src/modm/platform/extint/rp/module.md | 53 +++++++ src/modm/platform/gpio/rp/base.hpp.in | 46 ++++++ src/modm/platform/gpio/rp/module.lb | 15 +- 10 files changed, 511 insertions(+), 2 deletions(-) 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_handler.cpp.in create mode 100644 src/modm/platform/extint/rp/int_handler.hpp.in create mode 100644 src/modm/platform/extint/rp/int_priority.hpp 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..6a13eba3db 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ○ ○ -○ +✅ ✅ ✅ ✅ diff --git a/examples/rp_pico/interrupt/main.cpp b/examples/rp_pico/interrupt/main.cpp new file mode 100644 index 0000000000..9d449b8086 --- /dev/null +++ b/examples/rp_pico/interrupt/main.cpp @@ -0,0 +1,38 @@ +/* + * 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(); + + // Powers on the LED on the low->high transition, and off on high->low. + GpioInput0::setInput(); + IntHandler::connect(Gpio::InputTrigger::BothEdges, + [](Gpio::InputTrigger_t triggers) { + Led::set(!!(triggers & Gpio::InputTrigger::RisingEdge)); + }); + + // Toggles LED each time gpio input is at the high level. + GpioInput1::setInput(Gpio::InputType::PullDown); + IntHandler::connect(Gpio::InputTrigger::HighLevel, + [](Gpio::InputTrigger_t) { 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..9abf5ab623 --- /dev/null +++ b/examples/rp_pico/interrupt/project.xml @@ -0,0 +1,10 @@ + + modm:rp-pico + + + + + modm:platform:extint + modm:build:scons + + 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..8e38712fa3 --- /dev/null +++ b/src/modm/platform/extint/rp/int_handler.cpp.in @@ -0,0 +1,98 @@ +/* + * 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_bank0 +void +IntHandler::irqBank0Handler() +{ + using PortRegs = Gpio::PortRegs; + + static_assert(0b1111u == static_cast(Gpio::InputTrigger::All)); + +%% if multicore_enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? iobank0_hw->proc1_irq_ctrl : iobank0_hw->proc0_irq_ctrl; +%% else + auto& proc_irq_ctrl = iobank0_hw->proc0_irq_ctrl; +%% endif + + for (size_t group = 0; group < NUM_BANK0_GPIOS / 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 = bank0Handlers[pin]) + { + handler(static_cast(triggers)); + } + } + } + } + } +} + +%% endif + +%% if with_qspi +void +IntHandler::irqQspiHandler() +{ + using PortRegs = Gpio::PortRegs; + + static_assert(NUM_QSPI_GPIOS <= 8); + static_assert(0b1111u == static_cast(Gpio::InputTrigger::All)); + +%% if multicore_enabled + auto& proc_irq_ctrl = sio_hw->cpuid ? ioqspi_hw->proc1_qspi_ctrl : ioqspi_hw->proc0_qspi_ctrl; +%% else + auto& proc_irq_ctrl = ioqspi_hw->proc0_qspi_ctrl; +%% endif + + 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 = qspiHandlers[pin]) + { + handler(static_cast(triggers)); + } + } + } +} + +%% endif + +%% for type in types +IntHandler::Handler +IntHandler::{{type}}Handlers[NUM_{{type | upper}}_GPIOS] modm_fastdata; + +%% endfor + +%% for type in types +MODM_ISR(IO_IRQ_{{type | upper}}) +{ + IntHandler::irq{{type | capitalize}}Handler(); +} + +%% endfor + +} // 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..1d0da52676 --- /dev/null +++ b/src/modm/platform/extint/rp/int_handler.hpp.in @@ -0,0 +1,145 @@ +/* + * 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 + +#include + +#if defined __DOXYGEN__ || !defined MODM_EXTINT_HANDLER_STORAGE +/// @ingroup modm_platform_extint +#define MODM_EXTINT_HANDLER_STORAGE sizeof(void*) +#endif + +namespace modm::platform +{ + +%% for type in types +MODM_ISR_DECL(IO_IRQ_{{type | upper}}); +%% endfor + +/** + * Interrupt Handler + * + * @ingroup modm_platform_extint + */ +class IntHandler +{ +public: + using Handler = modm::inplace_function; + +public: + static void + enable(IRQn_Type type, IntPriority priority = IntPriority::Default) + { + if (!NVIC_GetEnableIRQ(type)) + { + if (priority != static_cast(NVIC_GetPriority(type))) + { + NVIC_SetPriority(type, priority); + } + NVIC_ClearPendingIRQ(type); + NVIC_EnableIRQ(type); + } + } + + static void + disable(IRQn_Type type) + { + NVIC_DisableIRQ(type); + } + + template + static void + connect(Gpio::InputTrigger_t triggers, Handler&& handler) + { + constexpr const auto type = irqType(); + static_assert(0 <= type, "Support for this Pin's Port is not enabled!"); + + enable(type); + + disableInterrupts(Gpio::InputTrigger::All); + acknowledgeInterrupts(Gpio::InputTrigger::All); + + irqHandler(Pin::pin) = handler; + + enableInterrupts(triggers); + } + + template + static void + disconnect() + { + static_assert(0 <= irqType(), "Support for this Pin's Port is not enabled!"); + + disableInterrupts(Gpio::InputTrigger::All); + irqHandler(Pin::pin) = nullptr; + } + +private: + template + static void + enableInterrupts(Gpio::InputTrigger_t triggers) + { + Gpio::PortRegs::enable_irq(Pin::pin, triggers); + } + + template + static void + disableInterrupts(Gpio::InputTrigger_t triggers) + { + Gpio::PortRegs::disable_irq(Pin::pin, triggers); + } + + template + static void + acknowledgeInterrupts(Gpio::InputTrigger_t triggers) + { + Gpio::PortRegs::acknowledge_irq(Pin::pin, triggers); + } + +%% for type in types + static void + irq{{type | capitalize}}Handler(); + friend void MODM_ISR_NAME(IO_IRQ_{{type | upper}})(); + + // In the current implementation we do not allow handlers + // for the same line (pin) for more than a single core. + static Handler {{type}}Handlers[NUM_{{type | upper}}_GPIOS]; + +%% endfor + + template + static constexpr IRQn_Type + irqType() + { +%% for type in types + if constexpr (port == Gpio::Port::{{type | capitalize}}) { return IO_IRQ_{{type | upper}}_IRQn; } +%% endfor + return static_cast(-99); + } + + template + static constexpr Handler& + irqHandler(uint8_t pin) + { +%% for type in types + if constexpr (port == Gpio::Port::{{type | capitalize}}) { return {{type}}Handlers[pin]; } +%% endfor + return *static_cast(nullptr); + } +}; + +} // namespace modm::platform \ No newline at end of file diff --git a/src/modm/platform/extint/rp/int_priority.hpp b/src/modm/platform/extint/rp/int_priority.hpp new file mode 100644 index 0000000000..22f1d5d77e --- /dev/null +++ b/src/modm/platform/extint/rp/int_priority.hpp @@ -0,0 +1,39 @@ +/* + * 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_extint + */ +enum IntPriority : uint8_t +{ + Highest = 0x00, + Default = 0x80, + Lowest = 0xff, +}; + +} // 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..57b1f8b4a7 --- /dev/null +++ b/src/modm/platform/extint/rp/module.lb @@ -0,0 +1,67 @@ +#!/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/. +# ----------------------------------------------------------------------------- + +from collections import namedtuple + +IntOpts = namedtuple('IntOpts', ['name', 'enabled_by_default']) + +int_types = { + "bank0": IntOpts(name="gpio", enabled_by_default=True), + "qspi": IntOpts(name="qspi", enabled_by_default=False), +} + + +def init(module): + module.name = ":platform:extint" + module.description = FileReader("module.md") + + +def prepare(module, options): + module.depends( + ":architecture:interrupt", + ":cmsis:device", + ":platform:gpio") + for _, opts in int_types.items(): + module.add_option( + BooleanOption( + name=opts.name, + description="Enable IRQ support for {}".format(opts.name.upper()), + default=opts.enabled_by_default)) + + return options[":target"].identifier.platform == "rp" + + +def validate(env): + type_ids = int_types.keys() + if not any([env[int_types[tid].name] for tid in type_ids]): + raise ValidateException("At least one of IRQ types [{}] must be enabled!" + .format(", ".join([int_types[tid].name for tid in type_ids]))) + + +def build(env): + multicore_enabled = env.has_module(":platform:multicore") + + type_ids = sorted(int_types.keys()) + + enabled_types = [tid for tid in type_ids if env[int_types[tid].name]] + + env.substitutions = { + "multicore_enabled": multicore_enabled, + "types": enabled_types, + } + + for tid in type_ids: + env.substitutions["with_{}".format(tid)] = tid in enabled_types + + env.outbasepath = "modm/src/modm/platform/extint" + env.copy("int_priority.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..4b66ba408a --- /dev/null +++ b/src/modm/platform/extint/rp/module.md @@ -0,0 +1,53 @@ +# External Interrupt Handler + +This driver provides an API for configuring all IRQ lines via register access. + +```cpp +// Powers on the LED on the low->high transition, and off on high->low. +GpioInput0::setInput(); +IntHandler::connect(Gpio::InputTrigger::BothEdges, + [](Gpio::InputTrigger_t triggers) { + Led::set(!!(triggers & Gpio::InputTrigger::RisingEdge)); + }); + +// Toggles LED each time gpio input is at the high level. +GpioInput1::setInput(Gpio::InputType::PullDown); +IntHandler::connect(Gpio::InputTrigger::HighLevel, + [](Gpio::InputTrigger_t) { 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 by defining a new global storage size +`MODM_EXTINT_HANDLER_STORAGE=bytes` in your `project.xml`: + +```xml + + + MODM_EXTINT_HANDLER_STORAGE=12 + + +``` + +## IRQ Types + +You can explicitly enable or disable handling of the specific IRQ type in your `project.xml`: + +```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..3cf164344e 100644 --- a/src/modm/platform/gpio/rp/base.hpp.in +++ b/src/modm/platform/gpio/rp/base.hpp.in @@ -14,6 +14,8 @@ #include #include + +#include #include %% for port in ports #include @@ -35,6 +37,22 @@ struct Gpio PullDown = 0x2, ///< pull-down on input }; + /// @ingroup modm_platform_extint + enum class + InputTrigger : uint32_t + { + None = 0x00u, + LowLevel = 0x01u, + HighLevel = 0x02u, + BothLevels = LowLevel | HighLevel, + FallingEdge = 0x04u, + RisingEdge = 0x08u, + BothEdges = FallingEdge | RisingEdge, + All = BothLevels | BothEdges, + }; + + MODM_FLAGS32(InputTrigger); + enum class OutputType { @@ -47,6 +65,7 @@ struct Gpio Slow = 0, Fast = 1, }; + enum class DriveStrength { @@ -55,6 +74,7 @@ struct Gpio mA_8 = 2, mA_12 = 3, }; + enum class SlewRate : uint8_t { @@ -133,7 +153,33 @@ struct Gpio::PortRegs PADS_BANK0_GPIO0_SLEWFAST_BITS ); } + static void enable_irq(uint8_t pin, InputTrigger_t triggers) + { + uint32_t value = triggers.value << 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_t triggers) + { + uint32_t value = triggers.value << 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_t triggers) + { + uint32_t value = triggers.value << 4 * {{intreg_pin_mask_shift[port]}}; + io{{port | lower}}_hw->intr{{intreg_pin_access[port]}} = value; + } }; + %% endfor /// @endcond diff --git a/src/modm/platform/gpio/rp/module.lb b/src/modm/platform/gpio/rp/module.lb index 6ae1fa611b..71b46e79cb 100644 --- a/src/modm/platform/gpio/rp/module.lb +++ b/src/modm/platform/gpio/rp/module.lb @@ -36,7 +36,7 @@ def init(module): def prepare(module, options): - module.depends(":architecture:gpio", ":cmsis:device", ":math:utils") + module.depends(":architecture:register", ":architecture:gpio", ":cmsis:device", ":math:utils") return options[":target"].has_driver("gpio:rp*") @@ -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"