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"