diff --git a/README.md b/README.md
index 4170220f31..b7a9debaa5 100644
--- a/README.md
+++ b/README.md
@@ -449,7 +449,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
○ |
-○ |
+✅ |
○ |
○ |
○ |
diff --git a/examples/samg55_xplained_pro/timer/main.cpp b/examples/samg55_xplained_pro/timer/main.cpp
new file mode 100644
index 0000000000..75423792c0
--- /dev/null
+++ b/examples/samg55_xplained_pro/timer/main.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * 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
+#include
+#include
+
+using namespace modm::platform;
+using namespace modm::literals;
+
+MODM_ISR(TC3)
+{
+ // Clear pending interrupts by reading them
+ (void)TimerChannel3::getInterruptFlags();
+
+ static bool flag = false;
+ if (flag)
+ {
+ TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Clear,
+ TimerChannel0::TioEffect::Set);
+ } else
+ {
+ TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Set,
+ TimerChannel0::TioEffect::Clear);
+ }
+ flag = !flag;
+}
+
+int
+main()
+{
+ /*
+ * This example uses channel 0 to generate two output waveforms, and channel3
+ * to create a periodic IRQ which swaps the polarity on the TIOB output.
+ *
+ * Note:
+ * On the SAMG55, the waveform outputs on the second module (TC1, TimerChannel[3-5])
+ * are not connected to external pins.
+ */
+ Board::initialize();
+
+ TimerChannel0::initialize();
+ TimerChannel0::connect();
+
+ TimerChannel0::setClockSource(TimerChannel0::ClockSource::MckDiv2);
+ TimerChannel0::setWaveformMode(true);
+ // Setup timer to count up, and trigger reset on Rc match
+ TimerChannel0::setWaveformSelection(TimerChannel0::WavSel::Up_Rc);
+
+ // Setup TioA to set on RA match, and clear on RC match
+ TimerChannel0::setTioaEffects(TimerChannel0::TioEffect::Set, TimerChannel0::TioEffect::Clear);
+ // Setup TioB to clear on RB match, and set on RC match
+ TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Clear, TimerChannel0::TioEffect::Set);
+
+ // Change external event source, so that TIOB can be used as an output
+ TimerChannel0::setExtEventSource(TimerChannel0::ExtEventSource::Xc0);
+
+ TimerChannel0::setRegA(10000);
+ TimerChannel0::setRegB(5000);
+ TimerChannel0::setRegC(15000);
+
+ TimerChannel0::enable();
+ TimerChannel0::start();
+
+ // Setup TC3 irq to swap TIOB polarity periodically
+ // Period = 128 * 10000 / 120MHz = ~10.6ms
+ TimerChannel3::initialize();
+ TimerChannel3::setClockSource(TimerChannel0::ClockSource::MckDiv128);
+ TimerChannel3::setRegC(10000);
+ TimerChannel3::setWaveformMode(true);
+ TimerChannel3::setWaveformSelection(TimerChannel0::WavSel::Up_Rc);
+ TimerChannel3::enableInterruptVector(true);
+ TimerChannel3::enableInterrupt(TimerChannel3::Interrupt::RcCompare);
+ TimerChannel3::enable();
+ TimerChannel3::start();
+
+ while (true)
+ ;
+}
\ No newline at end of file
diff --git a/examples/samg55_xplained_pro/timer/openocd.cfg b/examples/samg55_xplained_pro/timer/openocd.cfg
new file mode 100644
index 0000000000..233bc3ba94
--- /dev/null
+++ b/examples/samg55_xplained_pro/timer/openocd.cfg
@@ -0,0 +1,6 @@
+
+# Configure for ATSAMG55-XPRO dev board
+source [find interface/cmsis-dap.cfg]
+
+set CHIPNAME ATSAMG55J19
+source [find target/at91samg5x.cfg]
diff --git a/examples/samg55_xplained_pro/timer/project.xml b/examples/samg55_xplained_pro/timer/project.xml
new file mode 100644
index 0000000000..70dc0c60f9
--- /dev/null
+++ b/examples/samg55_xplained_pro/timer/project.xml
@@ -0,0 +1,14 @@
+
+ modm:samg55-xplained-pro
+
+
+
+
+
+ modm:build:scons
+ modm:platform:timer:0
+ modm:platform:timer:1
+ modm:platform:timer:2
+ modm:platform:timer:3
+
+
\ No newline at end of file
diff --git a/src/modm/board/samg55_xplained_pro/board.hpp b/src/modm/board/samg55_xplained_pro/board.hpp
index 078289d939..249ff052a4 100644
--- a/src/modm/board/samg55_xplained_pro/board.hpp
+++ b/src/modm/board/samg55_xplained_pro/board.hpp
@@ -27,7 +27,16 @@ struct SystemClock
static constexpr uint32_t Frequency = PllAMult * SlowClkFreqHz; // CPU core frequency
static constexpr uint32_t Usb = PllBMult * SlowClkFreqHz;
- static constexpr uint32_t Mck = Frequency; // Master clock, used by most peripherals
+ static constexpr uint32_t Mck = Frequency; // Master clock: default used by most peripherals
+ // Programmable clocks: optionally used by certain peripherals
+ static constexpr uint32_t Pck0 = Mck;
+ static constexpr uint32_t Pck1 = Mck;
+ static constexpr uint32_t Pck2 = Mck;
+ static constexpr uint32_t Pck3 = Mck;
+ static constexpr uint32_t Pck4 = Mck;
+ static constexpr uint32_t Pck5 = Mck;
+ static constexpr uint32_t Pck6 = Mck;
+ static constexpr uint32_t Pck7 = Mck;
static bool inline
enable()
diff --git a/src/modm/platform/timer/samg/module.lb b/src/modm/platform/timer/samg/module.lb
new file mode 100644
index 0000000000..b6283d9272
--- /dev/null
+++ b/src/modm/platform/timer/samg/module.lb
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021, Jeff McBride
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+props = {}
+class Instance(Module):
+ def __init__(self, driver, instance, channel):
+ self.driver = driver
+ self.instance = int(instance)
+ self.channel = channel
+ self.vectors = None
+
+ def init(self, module):
+ module.name = str(self.instance)
+ module.description = "Instance {}".format(self.instance)
+
+ def prepare(self, module, options):
+ return True
+
+ def build(self, env):
+ global props
+ props["id"] = self.instance
+ props["module"] = int(self.instance / 3)
+ props["channel"] = self.channel
+
+ env.substitutions = props
+ env.outbasepath = "modm/src/modm/platform/timer"
+ env.template("timer_channel.hpp.in", "timer_channel_{}.hpp".format(self.instance))
+
+def init(module):
+ module.name = ":platform:timer"
+ module.description = "Timers (TC)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("tc:samg*"):
+ return False
+
+ module.depends(
+ ":cmsis:device",
+ ":platform:gpio")
+
+ timers = device.get_all_drivers("tc:samg*")
+ for driver in timers:
+ for instance in driver["instance"]:
+ instance = int(instance)
+ # Each TC instance has 3 channels, and we create an independent driver for each
+ for channel in range(3):
+ module.add_submodule(Instance(driver, instance*3+channel, channel))
+
+ global props
+ device = options[":target"]
+ props["target"] = device.identifier
+
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/platform/timer"
+ env.copy("timer_channel_base.hpp")
\ No newline at end of file
diff --git a/src/modm/platform/timer/samg/timer_channel.hpp.in b/src/modm/platform/timer/samg/timer_channel.hpp.in
new file mode 100644
index 0000000000..4e0e6f45bc
--- /dev/null
+++ b/src/modm/platform/timer/samg/timer_channel.hpp.in
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * 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 "timer_channel_base.hpp"
+
+#include
+
+namespace modm::platform
+{
+
+/**
+ * @brief Timer Channel {{ id }}
+ *
+ * Driver for TC{{ module }} Channel {{ channel }}
+ *
+ * Each TC module provides three independent 16-bit timer channels, and each
+ * channel can generate two waveform outputs (TIOA/TIOB). In the datasheet
+ * and headers, TCx is sometimes used to refer to a module, and sometimes
+ * to a specific channel (for example, TC3_IRQn is the IRQ for TC1 channel 0).
+ *
+ * This driver does not cover all the functionality of the timer. For example,
+ * it does not (yet) support input capture or the block control that allows
+ * synchronization of the three timers within a module.
+ *
+ * To define an interrupt handler for this timer:
+ * \code
+ * MODM_ISR(TC{{ id }})
+ * {
+ * // Reading the interrupt flags clears them
+ * TimerChannel{{ id }}::Interrupt_t flags = TimerChannel{{ id }}::getInterruptFlags();
+ * }
+ * \endcode
+ *
+ * @author Jeff McBride
+ * @ingroup modm_platform_timer
+ */
+class TimerChannel{{ id }} : public TimerChannelBase
+{
+public:
+ template< class... Pins >
+ static void
+ connect()
+ {
+ using TioaPin = GetPin_t;
+ using TiobPin = GetPin_t;
+ using TclkPin = GetPin_t;
+ using Tc = Peripherals::Tc<{{ module | int }}>;
+
+ if constexpr (!std::is_void::value) {
+ using TioaConnector = typename TioaPin::template Connector>;
+ TioaConnector::connect();
+ }
+ if constexpr (!std::is_void::value) {
+ using TiobConnector = typename TiobPin::template Connector>;
+ TiobConnector::connect();
+ }
+ if constexpr (!std::is_void::value) {
+ using TclkConnector = typename TclkPin::template Connector>;
+ TclkConnector::connect();
+ }
+ }
+
+ /** Enable the peripheral clock
+ *
+ * This method must be called before any timer channel configuration can
+ * be done.
+ */
+ static void
+ initialize()
+ {
+ ClockGen::enable();
+ }
+
+ /** Select which clock source will be used for counting
+ */
+ static inline void
+ setClockSource(ClockSource src)
+ {
+ uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR & ~(TC_CMR_TCCLKS_Msk);
+ tmp |= (uint32_t)src << TC_CMR_TCCLKS_Pos;
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp;
+ }
+
+ /** Returns the currently selected clock source
+ */
+ static inline ClockSource
+ getClockSource()
+ {
+ uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR;
+ return (ClockSource)((cmr & TC_CMR_TCCLKS_Msk) >> TC_CMR_TCCLKS_Pos);
+ }
+
+ static inline void
+ setWaveformSelection(WavSel ws)
+ {
+ uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR;
+ cmr &= ~TC_CMR_WAVSEL_Msk;
+ cmr |= (uint32_t)ws << TC_CMR_WAVSEL_Pos;
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = cmr;
+ }
+
+ static inline void
+ enable()
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_CLKEN;
+ }
+
+ static inline void
+ disable()
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_CLKDIS;
+ }
+
+ static inline void
+ start()
+ {
+ softwareTrigger();
+ }
+
+ static inline uint16_t
+ getValue()
+ {
+ return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CV;
+ }
+
+ static inline void
+ setValue(uint16_t value)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CV = value;
+ }
+
+ static inline uint16_t
+ getRegA()
+ {
+ return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RA;
+ }
+
+ static inline void
+ setRegA(uint16_t value)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RA = value;
+ }
+
+ static inline uint16_t
+ getRegB()
+ {
+ return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RB;
+ }
+
+ static inline void
+ setRegB(uint16_t value)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RB = value;
+ }
+
+ static inline uint16_t
+ getRegC()
+ {
+ return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RC;
+ }
+
+ static inline void
+ setRegC(uint16_t value)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RC = value;
+ }
+
+ /** Return the frequency of the counter based on current configuration
+ *
+ * This method will return 0 if the counter is clocked from an external
+ * input.
+ */
+ template
+ static inline uint32_t
+ getTickFrequency()
+ {
+ switch(getClockSource())
+ {
+ case ClockSource::MckDiv2:
+ return SystemClock::Mck / 2;
+ case ClockSource::MckDiv8:
+ return SystemClock::Mck / 8;
+ case ClockSource::MckDiv32:
+ return SystemClock::Mck / 32;
+ case ClockSource::MckDiv128:
+ return SystemClock::Mck / 128;
+ case ClockSource::Pck3:
+ return SystemClock::Pck3;
+ default:
+ return 0; // Clocked externally, so we can't know the frequency
+ }
+ }
+
+ /** Enable the NVIC IRQ for this timer channel
+ */
+ static inline void enableInterruptVector(bool enable, uint32_t priority=5)
+ {
+ if(enable)
+ {
+ NVIC_SetPriority(TC{{ id }}_IRQn, priority);
+ NVIC_EnableIRQ(TC{{ id }}_IRQn);
+ } else {
+ NVIC_DisableIRQ(TC{{ id }}_IRQn);
+ }
+ }
+
+ /** Enable interrupt in peripheral interrupt mask register
+ */
+ static inline void enableInterrupt(Interrupt_t interrupts)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_IER = interrupts.value;
+ }
+
+ /** Disable interrupt in peripheral interrupt mask register
+ */
+ static inline void disableInterrupt(Interrupt_t interrupts)
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_IDR = interrupts.value;
+ }
+
+ /** Reads the currently pending interrupt flags
+ *
+ * Flags are automatically cleared in hardware when they are read
+ */
+ static inline Interrupt_t getInterruptFlags()
+ {
+ return (Interrupt_t)(TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_SR & 0x3f);
+ }
+
+ /** Generate a software trigger event */
+ static inline void softwareTrigger()
+ {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_SWTRG;
+ }
+
+ /** Enable or disable waveform mode
+ *
+ * When not in waveform mode, timer operates in capture mode.
+ */
+ static inline void setWaveformMode(bool enabled)
+ {
+ if(enabled) {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR |= TC_CMR_WAVE;
+ } else {
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR &= ~TC_CMR_WAVE;
+ }
+ }
+
+ /** Selects which input signal is used as an external event
+ *
+ * Note that by default, TIOB is selected, and this prevents TIOB from
+ * being used as a waveform output. To use it as an output, select one
+ * of the other input options
+ */
+ static inline void setExtEventSource(ExtEventSource src)
+ {
+ uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR;
+ cmr &= ~TC_CMR_EEVT_Msk;
+ cmr |= (uint32_t)src << TC_CMR_EEVT_Pos;
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = cmr;
+ }
+
+ /** Set the effect of events on the TIOA output in waveform mode
+ */
+ static inline void setTioaEffects(
+ TioEffect raCompare,
+ TioEffect rcCompare,
+ TioEffect extEvent = TioEffect::None,
+ TioEffect swTrig = TioEffect::None)
+ {
+ uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR;
+ tmp &= ~(TC_CMR_ACPA_Msk | TC_CMR_ACPC_Msk | TC_CMR_AEEVT_Msk | TC_CMR_ASWTRG_Msk);
+ tmp |= ((uint32_t)raCompare << TC_CMR_ACPA_Pos) |
+ ((uint32_t)rcCompare << TC_CMR_ACPC_Pos) |
+ ((uint32_t)extEvent << TC_CMR_AEEVT_Pos) |
+ ((uint32_t)swTrig << TC_CMR_ASWTRG_Pos);
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp;
+ }
+
+ /** Set the effect of events on the TIOB output in waveform mode
+ *
+ * Note that by default TIOB is configured as an input for external events.
+ * To use it as a waveform output, you just select a different external
+ * event input with setExtEventSource.
+ */
+ static inline void setTiobEffects(
+ TioEffect rbCompare,
+ TioEffect rcCompare,
+ TioEffect extEvent = TioEffect::None,
+ TioEffect swTrig = TioEffect::None)
+ {
+ uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR;
+ tmp &= ~(TC_CMR_BCPB_Msk | TC_CMR_BCPC_Msk | TC_CMR_BEEVT_Msk | TC_CMR_BSWTRG_Msk);
+ tmp |= ((uint32_t)rbCompare << TC_CMR_BCPB_Pos) |
+ ((uint32_t)rcCompare << TC_CMR_BCPC_Pos) |
+ ((uint32_t)extEvent << TC_CMR_BEEVT_Pos) |
+ ((uint32_t)swTrig << TC_CMR_BSWTRG_Pos);
+ TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp;
+ }
+
+};
+
+}; // namespace modm::platform
diff --git a/src/modm/platform/timer/samg/timer_channel_base.hpp b/src/modm/platform/timer/samg/timer_channel_base.hpp
new file mode 100644
index 0000000000..8657f7fb50
--- /dev/null
+++ b/src/modm/platform/timer/samg/timer_channel_base.hpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * 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 "../device.hpp"
+
+namespace modm::platform
+{
+
+class TimerChannelBase
+{
+public:
+ /// Effect of various events on the TioX output pins
+ enum class TioEffect : uint32_t
+ {
+ None = 0,
+ Set = 1,
+ Clear = 2,
+ Toggle = 3
+ };
+
+ /// External Event Selection values
+ enum class ExtEventSource : uint32_t
+ {
+ Tiob = 0,
+ Xc0 = 1,
+ Xc1 = 2,
+ Xc2 = 3,
+ };
+
+ /// Waveform mode selection values
+ enum class WavSel : uint32_t
+ {
+ Up = 0, // UP mode without automatic trigger on RC compare
+ UpDown = 1, // UPDOWN mode without automatic trigger on RC compare
+ Up_Rc = 2, // UP mode with automatic trigger on RC compare
+ UpDown_Rc = 3 // UPDOWN mode with automatic trigger on RC compare
+ };
+
+ /// Input clock selection values
+ enum class ClockSource : uint32_t
+ {
+ MckDiv2 = 0,
+ MckDiv8 = 1,
+ MckDiv32 = 2,
+ MckDiv128 = 3,
+ Pck3 = 4,
+ Xc0 = 5,
+ Xc1 = 6,
+ Xc2 = 7
+ };
+
+ /// Interrupt flags
+ enum class Interrupt : uint32_t
+ {
+ CounterOverflow = TC_IER_COVFS,
+ LoadOverrun = TC_IER_LOVRS,
+ RaCompare = TC_IER_CPAS,
+ RbCompare = TC_IER_CPBS,
+ RcCompare = TC_IER_CPCS,
+ RaLoading = TC_IER_LDRAS,
+ RbLoading = TC_IER_LDRBS,
+ ExternalTrigger = TC_IER_ETRGS,
+ EndOfRxTransfer = TC_IER_ENDRX,
+ RxBufferFull = TC_IER_RXBUFF,
+ };
+ MODM_FLAGS32(Interrupt);
+
+ // The CMR register has dual definitions: one for capture mode and one for
+ // waveform mode. However it seems that only the capture mode bit definitions
+ // made it into the CMSIS device header. Here, the missing waveform mode
+ // definitions are defined as needed.
+ static const uint32_t TC_CMR_EEVT_Pos = 10;
+ static const uint32_t TC_CMR_EEVT_Msk = (0x3) << TC_CMR_EEVT_Pos;
+ static const uint32_t TC_CMR_WAVSEL_Pos = 13;
+ static const uint32_t TC_CMR_WAVSEL_Msk = (0x3) << TC_CMR_WAVSEL_Pos;
+ static const uint32_t TC_CMR_ACPA_Pos = 16;
+ static const uint32_t TC_CMR_ACPA_Msk = (0x3) << TC_CMR_ACPA_Pos;
+ static const uint32_t TC_CMR_ACPC_Pos = 18;
+ static const uint32_t TC_CMR_ACPC_Msk = (0x3) << TC_CMR_ACPC_Pos;
+ static const uint32_t TC_CMR_AEEVT_Pos = 20;
+ static const uint32_t TC_CMR_AEEVT_Msk = (0x3) << TC_CMR_AEEVT_Pos;
+ static const uint32_t TC_CMR_ASWTRG_Pos = 22;
+ static const uint32_t TC_CMR_ASWTRG_Msk = (0x3) << TC_CMR_ASWTRG_Pos;
+ static const uint32_t TC_CMR_BCPB_Pos = 24;
+ static const uint32_t TC_CMR_BCPB_Msk = (0x3) << TC_CMR_BCPB_Pos;
+ static const uint32_t TC_CMR_BCPC_Pos = 26;
+ static const uint32_t TC_CMR_BCPC_Msk = (0x3) << TC_CMR_BCPC_Pos;
+ static const uint32_t TC_CMR_BEEVT_Pos = 28;
+ static const uint32_t TC_CMR_BEEVT_Msk = (0x3) << TC_CMR_BEEVT_Pos;
+ static const uint32_t TC_CMR_BSWTRG_Pos = 30;
+ static const uint32_t TC_CMR_BSWTRG_Msk = (0x3) << TC_CMR_BSWTRG_Pos;
+};
+
+} // namespace modm::platform
\ No newline at end of file