diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 790050743a..fc8bcd34fe 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -80,6 +80,10 @@ jobs:
if: always()
run: |
(cd examples && ../tools/scripts/examples_compile.py samv)
+ - name: Examples RP20 Devices
+ if: always()
+ run: |
+ (cd examples && ../tools/scripts/examples_compile.py rp_pico)
- name: Execute Python Scripts
if: always()
run: |
@@ -225,7 +229,7 @@ jobs:
- name: Quick compile HAL for Cortex-M Part 1
if: always()
run: |
- (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 0)
+ (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 0)
- name: Upload log artifacts
uses: actions/upload-artifact@v2
with:
@@ -245,7 +249,7 @@ jobs:
- name: Quick compile HAL for Cortex-M Part 2
if: always()
run: |
- (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 1)
+ (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 1)
- name: Upload log artifacts
uses: actions/upload-artifact@v2
with:
@@ -265,7 +269,7 @@ jobs:
- name: Quick compile HAL for Cortex-M Part 3
if: always()
run: |
- (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 2)
+ (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 2)
- name: Upload log artifacts
uses: actions/upload-artifact@v2
with:
@@ -285,7 +289,7 @@ jobs:
- name: Quick compile HAL for Cortex-M Part 4
if: always()
run: |
- (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 3)
+ (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 3)
- name: Upload log artifacts
uses: actions/upload-artifact@v2
with:
diff --git a/.gitmodules b/.gitmodules
index 5af60003c3..2ade0e5db4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -37,3 +37,6 @@
[submodule "ext/etlcpp/etl"]
path = ext/etlcpp/etl
url = https://github.com/modm-ext/etl-partial.git
+[submodule "ext/rp/pico-sdk"]
+ path = ext/rp/pico-sdk
+ url = https://github.com/modm-ext/pico-sdk-partial.git
diff --git a/README.md b/README.md
index 950327cb99..950dd1cd5e 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
|
STM32 |
SAM |
+RP |
AT |
Peripheral |
@@ -120,6 +121,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
D21 |
G55 |
V70 |
+20 |
90 |
Mega |
Tiny |
@@ -142,6 +144,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
○ |
○ |
+○ |
✅ |
✅ |
@@ -162,6 +165,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
○ |
+✕ |
○ |
○ |
✕ |
@@ -183,6 +187,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
○ |
✕ |
○ |
+✕ |
○ |
○ |
○ |
@@ -205,6 +210,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
○ |
✕ |
+✕ |
○ |
✕ |
@@ -225,6 +231,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
○ |
✕ |
○ |
+✅ |
✕ |
✕ |
✕ |
@@ -249,6 +256,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
✕ |
+✕ |
External Interrupt |
✅ |
@@ -267,6 +275,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
○ |
○ |
+○ |
✅ |
✅ |
✅ |
@@ -288,6 +297,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
○ |
+○ |
✕ |
✕ |
✕ |
@@ -312,6 +322,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
✅ |
+✅ |
I2C |
✅ |
@@ -330,6 +341,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
○ |
○ |
○ |
+○ |
✅ |
✅ |
✅ |
@@ -354,6 +366,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
✕ |
+✕ |
Random Generator |
✕ |
@@ -375,6 +388,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
✕ |
+✕ |
SPI |
✅ |
@@ -396,6 +410,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
✅ |
+✅ |
System Clock |
✅ |
@@ -412,8 +427,9 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
✅ |
-✕ |
-✕ |
+✅ |
+✅ |
+✅ |
✕ |
✕ |
✕ |
@@ -438,6 +454,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
○ |
○ |
○ |
+○ |
UART |
✅ |
@@ -458,6 +475,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
○ |
✅ |
✅ |
+✅ |
○ |
Unique ID |
@@ -480,6 +498,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✕ |
✕ |
✕ |
+✕ |
USB |
✅ |
@@ -498,6 +517,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
○ |
+✅ |
✕ |
✕ |
✕ |
@@ -585,13 +605,15 @@ We have out-of-box support for many development boards including documentation.
NUCLEO-L552ZE-Q |
OLIMEXINO-STM32 |
+RP-PICO |
Raspberry Pi |
SAMD21-MINI |
-SAMG55-XPLAINED-PRO |
+SAMG55-XPLAINED-PRO |
STM32-F4VE |
STM32F030-DEMO |
Smart Response XE |
+
diff --git a/examples/generic/delay/project.xml b/examples/generic/delay/project.xml
index 39819a8090..44b756ceb7 100644
--- a/examples/generic/delay/project.xml
+++ b/examples/generic/delay/project.xml
@@ -17,6 +17,7 @@
+
diff --git a/examples/generic/usb/project.xml b/examples/generic/usb/project.xml
index 18a8b42256..aa57bea6b0 100644
--- a/examples/generic/usb/project.xml
+++ b/examples/generic/usb/project.xml
@@ -9,6 +9,7 @@
+
diff --git a/examples/rp_pico/blink/main.cpp b/examples/rp_pico/blink/main.cpp
new file mode 100644
index 0000000000..d2015d36b8
--- /dev/null
+++ b/examples/rp_pico/blink/main.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016, Sascha Schade
+ * Copyright (c) 2017, Niklas Hauser
+ *
+ * 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
+
+using namespace Board;
+
+/*
+ * Blinks the green user LED with 1 Hz.
+ * It is on for 90% of the time and off for 10% of the time.
+ */
+
+int
+main()
+{
+ Board::initialize();
+
+ LedGreen::setOutput();
+
+ while (true)
+ {
+ LedGreen::set();
+ modm::delay(900ms);
+
+ LedGreen::reset();
+ modm::delay(100ms);
+ }
+
+ return 0;
+}
diff --git a/examples/rp_pico/blink/project.xml b/examples/rp_pico/blink/project.xml
new file mode 100644
index 0000000000..8aff40d49a
--- /dev/null
+++ b/examples/rp_pico/blink/project.xml
@@ -0,0 +1,10 @@
+
+ modm:rp-pico
+
+
+
+
+
+ modm:build:scons
+
+
diff --git a/examples/rp_pico/fiber/main.cpp b/examples/rp_pico/fiber/main.cpp
new file mode 100644
index 0000000000..e322e5eb6e
--- /dev/null
+++ b/examples/rp_pico/fiber/main.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+#include
+
+using namespace Board;
+using namespace std::chrono_literals;
+
+// Create an IODeviceWrapper around the Uart Peripheral we want to use
+modm::IODeviceWrapper loggerDevice;
+
+// Set all four logger streams to use the UART
+modm::log::Logger modm::log::debug(loggerDevice);
+modm::log::Logger modm::log::info(loggerDevice);
+modm::log::Logger modm::log::warning(loggerDevice);
+modm::log::Logger modm::log::error(loggerDevice);
+
+constexpr uint32_t cycles = 1'000'000;
+
+static multicore::Mutex log_mutex;
+
+struct CoreData
+{
+ uint32_t total_counter = 0;
+ uint32_t f1counter = 0;
+ uint32_t f2counter = 0;
+};
+
+void
+fiber_function1(CoreData& d)
+{
+ while (++d.f1counter < cycles)
+ {
+ modm::fiber::yield();
+ d.total_counter++;
+ }
+}
+
+void
+fiber_function2(CoreData& d)
+{
+ while (++d.f2counter < cycles)
+ {
+ modm::fiber::yield();
+ d.total_counter++;
+ }
+}
+
+// put cores to mostly equalent environment
+modm_core0_data CoreData d0;
+modm_core1_data CoreData d1;
+
+modm_core0_noinit modm::fiber::Stack<384> stack01;
+modm_core0_noinit modm::fiber::Stack<384> stack02;
+modm_core1_noinit modm::fiber::Stack<384> stack11;
+modm_core1_noinit modm::fiber::Stack<384> stack12;
+
+modm_core0_data
+modm::Fiber fiber01(stack01, []() { fiber_function1(d0); }, 0);
+modm_core0_data
+modm::Fiber fiber02(stack02, []() { fiber_function2(d0); }, 0);
+modm_core1_data
+modm::Fiber fiber11(stack11, []() { fiber_function1(d1); }, 1);
+modm_core1_data
+modm::Fiber fiber12(stack12, []() { fiber_function2(d1); }, 1);
+
+template
+static void
+print_result(const CoreData& d, TimeDiff diff)
+{
+ std::lock_guard g(log_mutex);
+ MODM_LOG_INFO << "Benchmark for core" << multicore::Core::cpuId() << " done!" << modm::endl;
+ MODM_LOG_INFO << "Executed " << d.total_counter << " yields in " << diff << modm::endl;
+ MODM_LOG_INFO << ((d.total_counter * 1'000'000ull) / std::chrono::microseconds(diff).count());
+ MODM_LOG_INFO << " yields per second, ";
+ MODM_LOG_INFO << (std::chrono::nanoseconds(diff).count() / d.total_counter);
+ MODM_LOG_INFO << "ns per yield" << modm::endl;
+ MODM_LOG_INFO.flush();
+}
+
+void
+core1_main()
+{
+ const modm::PreciseTimestamp start = modm::PreciseClock::now();
+ modm::fiber::Scheduler::run();
+ const auto diff = (modm::PreciseClock::now() - start);
+
+ print_result(d1, diff);
+ while (true) __NOP();
+}
+
+int
+main()
+{
+ Board::initialize();
+
+ // initialize Uart0 for MODM_LOG_*
+ Uart0::connect();
+ Uart0::initialize();
+
+ MODM_LOG_INFO << "Starting fiber modm::yield benchmark..." << modm::endl;
+ MODM_LOG_INFO.flush();
+
+ multicore::Core1::run(core1_main);
+
+ const modm::PreciseTimestamp start = modm::PreciseClock::now();
+ modm::fiber::Scheduler::run();
+ const auto diff = (modm::PreciseClock::now() - start);
+
+ print_result(d0, diff);
+ while (true) __NOP();
+
+ return 0;
+}
diff --git a/examples/rp_pico/fiber/project.xml b/examples/rp_pico/fiber/project.xml
new file mode 100644
index 0000000000..48fe39bab5
--- /dev/null
+++ b/examples/rp_pico/fiber/project.xml
@@ -0,0 +1,15 @@
+
+ modm:rp-pico
+
+
+
+
+
+ modm:debug
+ modm:platform:uart:0
+ modm:platform:multicore
+ modm:build:scons
+ modm:processing:timer
+ modm:processing:fiber
+
+
diff --git a/examples/rp_pico/logger/main.cpp b/examples/rp_pico/logger/main.cpp
new file mode 100644
index 0000000000..21f840fab6
--- /dev/null
+++ b/examples/rp_pico/logger/main.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011, Fabian Greif
+ * Copyright (c) 2013, Kevin Läufer
+ * Copyright (c) 2013-2017, Niklas Hauser
+ * Copyright (c) 2014, 2016, Sascha Schade
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+#include
+
+// ----------------------------------------------------------------------------
+// Set the log level
+#undef MODM_LOG_LEVEL
+#define MODM_LOG_LEVEL modm::log::INFO
+
+// Create an IODeviceWrapper around the Uart Peripheral we want to use
+modm::IODeviceWrapper loggerDevice;
+
+// Set all four logger streams to use the UART
+modm::log::Logger modm::log::debug(loggerDevice);
+modm::log::Logger modm::log::info(loggerDevice);
+modm::log::Logger modm::log::warning(loggerDevice);
+modm::log::Logger modm::log::error(loggerDevice);
+
+class BlinkThread : public modm::pt::Protothread
+{
+public:
+ BlinkThread() { timeout.restart(100ms); }
+
+ bool
+ update()
+ {
+ PT_BEGIN();
+
+ while (true)
+ {
+ Board::LedGreen::reset();
+
+ PT_WAIT_UNTIL(timeout.isExpired());
+ timeout.restart(100ms);
+
+ Board::LedGreen::set();
+
+ PT_WAIT_UNTIL(timeout.isExpired());
+ timeout.restart(900ms);
+
+ MODM_LOG_INFO << "Seconds since reboot: " << ++uptime << modm::endl;
+ }
+
+ PT_END();
+ }
+
+private:
+ modm::ShortTimeout timeout;
+ uint32_t uptime;
+};
+
+BlinkThread blinkThread;
+
+// ----------------------------------------------------------------------------
+int
+main()
+{
+ Board::initialize();
+
+ // initialize Uart0 for MODM_LOG_*
+ Uart0::connect();
+ Uart0::initialize();
+
+ // Use the logging streams to print some messages.
+ // Change MODM_LOG_LEVEL above to enable or disable these messages
+ MODM_LOG_DEBUG << "debug" << modm::endl;
+ MODM_LOG_INFO << "info" << modm::endl;
+ MODM_LOG_WARNING << "warning" << modm::endl;
+ MODM_LOG_ERROR << "error" << modm::endl;
+
+ while (true) { blinkThread.update(); }
+
+ return 0;
+}
diff --git a/examples/rp_pico/logger/project.xml b/examples/rp_pico/logger/project.xml
new file mode 100644
index 0000000000..82492276ab
--- /dev/null
+++ b/examples/rp_pico/logger/project.xml
@@ -0,0 +1,14 @@
+
+ modm:rp-pico
+
+
+
+
+ modm:debug
+ modm:platform:gpio
+ modm:platform:uart:0
+ modm:processing:timer
+ modm:processing:protothread
+ modm:build:scons
+
+
diff --git a/examples/rp_pico/mclogger/main.cpp b/examples/rp_pico/mclogger/main.cpp
new file mode 100644
index 0000000000..17fec27ffa
--- /dev/null
+++ b/examples/rp_pico/mclogger/main.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+#include
+#include
+
+// ----------------------------------------------------------------------------
+// Set the log level
+#undef MODM_LOG_LEVEL
+#define MODM_LOG_LEVEL modm::log::INFO
+
+// Create an IODeviceWrapper around the Uart Peripheral we want to use
+modm::IODeviceWrapper loggerDevice;
+
+// Set all four logger streams to use the UART
+modm::log::Logger modm::log::debug(loggerDevice);
+modm::log::Logger modm::log::info(loggerDevice);
+modm::log::Logger modm::log::warning(loggerDevice);
+modm::log::Logger modm::log::error(loggerDevice);
+
+//
+#if 0
+// with simple case we can use directly SpinLockUnsafe
+using LockMutex = modm::platform::multicore::SpinLockUnsafe<1>;
+#define LOG_GUARD() modm::platform::multicore::SpinLockGuard g
+#define INIT_GUARD() LockMutex::init()
+#else
+// more extensive, but work at any case
+static multicore::Mutex log_mutex;
+#define LOG_GUARD() std::lock_guard g(log_mutex)
+#define INIT_GUARD() \
+ do { \
+ } while (false)
+#endif
+
+template
+class Thread : public modm::pt::Protothread
+{
+ static constexpr auto delay = 10ms + 1ms * Instance;
+
+public:
+ Thread() { timeout.restart(delay); }
+
+ bool
+ update()
+ {
+ PT_BEGIN();
+ while (true)
+ {
+ PT_WAIT_UNTIL(timeout.isExpired());
+ timeout.restart(delay);
+ {
+ // try without this line for intermixed output
+ LOG_GUARD();
+ MODM_LOG_INFO << "Core: " << multicore::Core::cpuId()
+ << " thread: " << Instance << " uptime: " << ++uptime << modm::endl;
+ }
+ }
+
+ PT_END();
+ }
+
+private:
+ modm::ShortTimeout timeout;
+ uint32_t uptime;
+};
+
+template
+class Threads
+{
+private:
+ Thread t0;
+ Thread t1;
+ Thread t2;
+ Thread t3;
+
+public:
+ void
+ update()
+ {
+ t0.update();
+ t1.update();
+ t2.update();
+ t3.update();
+ }
+};
+
+Threads<0> core0;
+Threads<1> core1;
+
+void
+core1_main()
+{
+ while (true) { core1.update(); }
+}
+
+// ----------------------------------------------------------------------------
+int
+main()
+{
+ Board::initialize();
+
+ // initialize Uart0 for MODM_LOG_*
+ Uart0::connect();
+ Uart0::initialize();
+
+ // Use the logging streams to print some messages.
+ // Change MODM_LOG_LEVEL above to enable or disable these messages
+ MODM_LOG_DEBUG << "debug" << modm::endl;
+ MODM_LOG_INFO << "info" << modm::endl;
+ MODM_LOG_WARNING << "warning" << modm::endl;
+ MODM_LOG_ERROR << "error" << modm::endl;
+
+ INIT_GUARD();
+
+ multicore::Core1::run(core1_main);
+
+ while (true) { core0.update(); }
+
+ return 0;
+}
diff --git a/examples/rp_pico/mclogger/project.xml b/examples/rp_pico/mclogger/project.xml
new file mode 100644
index 0000000000..8f05615fd8
--- /dev/null
+++ b/examples/rp_pico/mclogger/project.xml
@@ -0,0 +1,15 @@
+
+ modm:rp-pico
+
+
+
+
+ modm:debug
+ modm:platform:gpio
+ modm:platform:uart:0
+ modm:platform:multicore
+ modm:processing:timer
+ modm:processing:protothread
+ modm:build:scons
+
+
diff --git a/examples/rp_pico/spi_dma/main.cpp b/examples/rp_pico/spi_dma/main.cpp
new file mode 100644
index 0000000000..8f4adf58ba
--- /dev/null
+++ b/examples/rp_pico/spi_dma/main.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Mike Wolfram
+ * Copyright (c) 2021, Raphael Lehmann
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+// Create an IODeviceWrapper around the UsbCDC
+modm::IODeviceWrapper usb_io_device0;
+modm::IOStream usb_stream0(usb_io_device0);
+
+// connect Pin3 to Pin4 for loopback
+using Mosi = GpioOutput3;
+using Miso = GpioInput4;
+using Sck = GpioOutput2;
+using DmaRx = Dma::Channel1;
+using DmaTx = Dma::Channel2;
+using Spi = SpiMaster0_Dma;
+
+int main()
+{
+ Board::initialize();
+ Board::initializeUsbFs();
+ tusb_init();
+
+ Spi::connect();
+ Spi::initialize();
+
+ while (true)
+ {
+ uint8_t sendBuffer[13] { "data to send" };
+ uint8_t receiveBuffer[13];
+ usb_stream0 << "Spi tx only" << modm::endl;
+ tud_task();
+ // send out 12 bytes, don't care about response
+ Spi::transferBlocking(sendBuffer, nullptr, 12);
+
+ usb_stream0 << "Spi send \"" << reinterpret_cast(sendBuffer) << "\"" << modm::endl;
+ tud_task();
+ // send out 12 bytes, read in 12 bytes
+ Spi::transferBlocking(sendBuffer, receiveBuffer, 12);
+ receiveBuffer[12] = 0;
+ usb_stream0 << "Spi received \"" << reinterpret_cast(receiveBuffer) << "\"" << modm::endl;
+ tud_task();
+ }
+
+ return 0;
+}
diff --git a/examples/rp_pico/spi_dma/project.xml b/examples/rp_pico/spi_dma/project.xml
new file mode 100644
index 0000000000..b1c9b09f08
--- /dev/null
+++ b/examples/rp_pico/spi_dma/project.xml
@@ -0,0 +1,15 @@
+
+ modm:rp-pico
+
+
+
+
+
+ modm:platform:gpio
+ modm:platform:dma
+ modm:platform:spi:0
+ modm:tinyusb
+ modm:io
+ modm:build:scons
+
+
diff --git a/ext/hathach/module.lb b/ext/hathach/module.lb
index 9acb160e9f..75dc0c24ae 100644
--- a/ext/hathach/module.lb
+++ b/ext/hathach/module.lb
@@ -104,6 +104,11 @@ def build(env):
tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAM{}{}".format(target.family.upper(), target.series.upper())
env.copy("tinyusb/src/portable/microchip/samd/", "portable/microchip/samd/")
+ elif target.platform == "rp":
+ if target.family == "20":
+ tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_RP2040"
+ env.copy("tinyusb/src/portable/raspberrypi/rp2040/", "portable/raspberrypi/rp2040/")
+
if env.has_module(":freertos"):
tusb_config["CFG_TUSB_OS"] = "OPT_OS_FREERTOS"
env.copy("tinyusb/src/osal/osal_freertos.h", "osal/osal_freertos.h")
@@ -165,6 +170,8 @@ def build(env):
irqs = irq_data["port_irqs"][speed]
elif target.platform == "sam" and target.family in ["g"]:
irqs = ["UDP"]
+ elif target.platform == "rp" and target.family in ["20"]:
+ irqs = ["USBCTRL_IRQ"]
else:
irqs = ["USB"]
diff --git a/ext/hathach/tusb_port.cpp.in b/ext/hathach/tusb_port.cpp.in
index 01693d699a..37b8a669c2 100644
--- a/ext/hathach/tusb_port.cpp.in
+++ b/ext/hathach/tusb_port.cpp.in
@@ -40,6 +40,10 @@ tusb_get_device_serial(uint16_t *serial_str)
(uint32_t *)0x0080A00C, (uint32_t *)0x0080A040,
(uint32_t *)0x0080A044, (uint32_t *)0x0080A048
%% endif
+%% elif target.platform in ["rp"]
+ /* sysinfo CHIP_ID and GIT_REV */
+ ((uint32_t *)0x40000000),((uint32_t *)0x40000040),
+ ((uint32_t *)0x40000000),((uint32_t *)0x40000040),
%% endif
};
diff --git a/ext/modm-devices b/ext/modm-devices
index 13e82a8511..76c5025686 160000
--- a/ext/modm-devices
+++ b/ext/modm-devices
@@ -1 +1 @@
-Subproject commit 13e82a8511877879bf99ff6b8d5af3b29d5b3204
+Subproject commit 76c5025686a95f3ceb97f658f0ddf1cd4ebdfc6f
diff --git a/ext/rp/README.md b/ext/rp/README.md
new file mode 100644
index 0000000000..ebb4f30886
--- /dev/null
+++ b/ext/rp/README.md
@@ -0,0 +1,45 @@
+## RP2040 device headers
+
+This folder includes the CMSIS device headers for RP2040 devices.
+The files included here are part of the CMSIS libraries for the particular chip.
+Only the device header and system init header from the `inc` folder are copied.
+
+## License
+
+The Raspberry Pi header files in this directory are available under the BSD-3-Clause License:
+```
+Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
+
+SPDX-License-Identifier: BSD-3-Clause
+```
+
+The Raspberry Pi CMSIS header files in this directory are available under
+the Apache-2.0/BSD-3-Clause License:
+```
+Copyright (c) 2009-2021 Arm Limited. All rights reserved.
+Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
+
+SPDX-License-Identifier: Apache-2.0
+
+Licensed under the Apache License, Version 2.0 (the License); you may
+not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an AS IS BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+SPDX-License-Identifier: BSD-3-Clause
+
+```
+
+The Raspberry Pi bootloader files available under BSD-3-Clause License:
+```
+Second stage boot code
+Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
+SPDX-License-Identifier: BSD-3-Clause
+```
\ No newline at end of file
diff --git a/ext/rp/address_mapped.h b/ext/rp/address_mapped.h
new file mode 100644
index 0000000000..b2d3d67c33
--- /dev/null
+++ b/ext/rp/address_mapped.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _HARDWARE_ADDRESS_MAPPED_H
+#define _HARDWARE_ADDRESS_MAPPED_H
+
+#ifdef __cplusplus
+#include
+#include
+#endif
+
+#define invalid_params_if(x, test) \
+ if (0) {}
+#define valid_params_if(x, test) \
+ if (0) {}
+#define hard_assert_if(x, test) \
+ if (0) {}
+
+#include "hardware/regs/addressmap.h"
+
+/** \file address_mapped.h
+ * \defgroup hardware_base hardware_base
+ *
+ * Low-level types and (atomic) accessors for memory-mapped hardware registers
+ *
+ * `hardware_base` defines the low level types and access functions for memory mapped hardware
+ registers. It is included
+ * by default by all other hardware libraries.
+ *
+ * The following register access typedefs codify the access type (read/write) and the bus size
+ (8/16/32) of the hardware register.
+ * The register type names are formed by concatenating one from each of the 3 parts A, B, C
+
+ * A | B | C | Meaning
+ * ------|---|---|--------
+ * io_ | | | A Memory mapped IO register
+ * |ro_| | read-only access
+ * |rw_| | read-write access
+ * |wo_| | write-only access (can't actually be enforced via C API)
+ * | | 8| 8-bit wide access
+ * | | 16| 16-bit wide access
+ * | | 32| 32-bit wide access
+ *
+ * When dealing with these types, you will always use a pointer, i.e. `io_rw_32 *some_reg` is a
+ pointer to a read/write
+ * 32 bit register that you can write with `*some_reg = value`, or read with `value = *some_reg`.
+ *
+ * RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the
+ bits within
+ * a hardware register so that concurrent access by two cores is always consistent with one atomic
+ operation
+ * being performed first, followed by the second.
+ *
+ * See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to
+ a 32 bit register
+ *
+ * Additionally given a pointer to a structure representing a piece of hardware (e.g. `dma_hw_t
+ *dma_hw` for the DMA controller), you can
+ * get an alias to the entire structure such that writing any member (register) within the
+ structure is equivalent
+ * to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()...
+ *
+ * For example `hw_set_alias(dma_hw)->inte1 = 0x80;` will set bit 7 of the INTE1 register of the
+ DMA controller,
+ * leaving the other bits unchanged.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define check_hw_layout(type, member, offset) \
+ static_assert(offsetof(type, member) == (offset), "hw offset mismatch")
+#define check_hw_size(type, size) static_assert(sizeof(type) == (size), "hw size mismatch")
+
+typedef volatile uint32_t io_rw_32;
+typedef const volatile uint32_t io_ro_32;
+typedef volatile uint32_t io_wo_32;
+typedef volatile uint16_t io_rw_16;
+typedef const volatile uint16_t io_ro_16;
+typedef volatile uint16_t io_wo_16;
+typedef volatile uint8_t io_rw_8;
+typedef const volatile uint8_t io_ro_8;
+typedef volatile uint8_t io_wo_8;
+
+typedef volatile uint8_t *const ioptr;
+typedef ioptr const const_ioptr;
+
+// A non-functional (empty) helper macro to help IDEs follow links from the autogenerated
+// hardware struct headers in hardware/structs/xxx.h to the raw register definitions
+// in hardware/regs/xxx.h. A preprocessor define such as TIMER_TIMEHW_OFFSET (a timer register
+// offset) is not generally clickable (in an IDE) if placed in a C comment, so
+// _REG_(TIMER_TIMEHW_OFFSET) is included outside of a comment instead
+#define _REG_(x)
+
+// Helper method used by hw_alias macros to optionally check input validity
+#define hw_alias_check_addr(addr) ((uintptr_t)(addr))
+// can't use the following impl as it breaks existing static declarations using hw_alias, so would
+// be a backwards incompatibility
+// static __force_inline uint32_t hw_alias_check_addr(volatile void *addr) {
+// uint32_t rc = (uintptr_t)addr;
+// invalid_params_if(ADDRESS_ALIAS, rc < 0x40000000); // catch likely non HW pointer types
+// return rc;
+//}
+
+// Helper method used by xip_alias macros to optionally check input validity
+modm_always_inline static uint32_t
+xip_alias_check_addr(const void *addr)
+{
+ uint32_t rc = (uintptr_t)addr;
+ valid_params_if(ADDRESS_ALIAS, rc >= XIP_MAIN_BASE && rc < XIP_NOALLOC_BASE);
+ return rc;
+}
+
+// Untyped conversion alias pointer generation macros
+#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS | hw_alias_check_addr(addr)))
+#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS | hw_alias_check_addr(addr)))
+#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS | hw_alias_check_addr(addr)))
+#define xip_noalloc_alias_untyped(addr) ((void *)(XIP_NOALLOC_BASE | xip_alias_check_addr(addr)))
+#define xip_nocache_alias_untyped(addr) ((void *)(XIP_NOCACHE_BASE | xip_alias_check_addr(addr)))
+#define xip_nocache_noalloc_alias_untyped(addr) \
+ ((void *)(XIP_NOCACHE_NOALLOC_BASE | xip_alias_check_addr(addr)))
+
+// Typed conversion alias pointer generation macros
+#define hw_set_alias(p) ((typeof(p))hw_set_alias_untyped(p))
+#define hw_clear_alias(p) ((typeof(p))hw_clear_alias_untyped(p))
+#define hw_xor_alias(p) ((typeof(p))hw_xor_alias_untyped(p))
+#define xip_noalloc_alias(p) ((typeof(p))xip_noalloc_alias_untyped(p))
+#define xip_nocache_alias(p) ((typeof(p))xip_nocache_alias_untyped(p))
+#define xip_nocache_noalloc_alias(p) ((typeof(p))xip_nocache_noalloc_alias_untyped(p))
+
+/*! \brief Atomically set the specified bits to 1 in a HW register
+ * \ingroup hardware_base
+ *
+ * \param addr Address of writable register
+ * \param mask Bit-mask specifying bits to set
+ */
+modm_always_inline static void
+hw_set_bits(io_rw_32 *addr, uint32_t mask)
+{
+ *(io_rw_32 *)hw_set_alias_untyped((volatile void *)addr) = mask;
+}
+
+/*! \brief Atomically clear the specified bits to 0 in a HW register
+ * \ingroup hardware_base
+ *
+ * \param addr Address of writable register
+ * \param mask Bit-mask specifying bits to clear
+ */
+modm_always_inline static void
+hw_clear_bits(io_rw_32 *addr, uint32_t mask)
+{
+ *(io_rw_32 *)hw_clear_alias_untyped((volatile void *)addr) = mask;
+}
+
+/*! \brief Atomically flip the specified bits in a HW register
+ * \ingroup hardware_base
+ *
+ * \param addr Address of writable register
+ * \param mask Bit-mask specifying bits to invert
+ */
+modm_always_inline static void
+hw_xor_bits(io_rw_32 *addr, uint32_t mask)
+{
+ *(io_rw_32 *)hw_xor_alias_untyped((volatile void *)addr) = mask;
+}
+
+/*! \brief Set new values for a sub-set of the bits in a HW register
+ * \ingroup hardware_base
+ *
+ * Sets destination bits to values specified in \p values, if and only if corresponding bit in \p
+ * write_mask is set
+ *
+ * Note: this method allows safe concurrent modification of *different* bits of
+ * a register, but multiple concurrent access to the same bits is still unsafe.
+ *
+ * \param addr Address of writable register
+ * \param values Bits values
+ * \param write_mask Mask of bits to change
+ */
+modm_always_inline static void
+hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask)
+{
+ hw_xor_bits(addr, (*addr ^ values) & write_mask);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ext/rp/device.hpp.in b/ext/rp/device.hpp.in
new file mode 100644
index 0000000000..20811e464d
--- /dev/null
+++ b/ext/rp/device.hpp.in
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2009, Martin Rosekeit
+ * Copyright (c) 2009, Thorsten Lajewski
+ * Copyright (c) 2009-2010, 2016, Fabian Greif
+ * Copyright (c) 2012-2013, 2016, Niklas Hauser
+ * Copyright (c) 2013, Kevin Laeufer
+ *
+ * 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/.
+ */
+// ----------------------------------------------------------------------------
+
+#ifndef MODM_DEVICE_HPP
+#define MODM_DEVICE_HPP
+
+%% for define in defines:
+#define {{ define }} 1
+%% endfor
+
+#include
+// Defines for example the modm_always_inline macro
+#include
+
+// Include external device headers:
+%% for header in headers
+#include <{{ header }}>
+%% endfor
+
+#endif // MODM_DEVICE_HPP
diff --git a/ext/rp/irq.h b/ext/rp/irq.h
new file mode 100644
index 0000000000..8b687a8554
--- /dev/null
+++ b/ext/rp/irq.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+/**
+ * Pico SDK compatibility for tinyusb
+ */
+#include "hardware/regs/intctrl.h"
+
+static inline void
+irq_set_enabled(int irqn, bool enable)
+{
+ if (enable) NVIC_EnableIRQ(irqn);
+ else NVIC_DisableIRQ(irqn);
+}
+
+static inline void
+irq_set_exclusive_handler(int irqn, void (*handler)())
+{
+ (void)irqn;
+ (void)handler;
+ // do nothing, irq implemented at modm
+}
diff --git a/ext/rp/module.lb b/ext/rp/module.lb
new file mode 100644
index 0000000000..2992164efe
--- /dev/null
+++ b/ext/rp/module.lb
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+import os
+
+def init(module):
+ module.name = ":cmsis:device"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.identifier["platform"] == "rp":
+ return False
+
+ module.depends(":cmsis:core")
+ return True
+
+def build(env):
+ env.collect(":build:path.include", "modm/ext/cmsis_device")
+
+ device = env[":target"]
+
+ env.outbasepath = "modm/ext/cmsis_device"
+
+ defines = []
+ headers = [
+ device.identifier.string.upper() + ".h"
+ ]
+
+ env.copy("pico-sdk/include", "")
+ env.copy("address_mapped.h", "hardware/address_mapped.h")
+
+ if env.has_module(":tinyusb"):
+ env.substitutions = {
+ "with_debug": env.has_module(":debug")
+ }
+ env.copy("pico.h")
+ env.template("pico.cpp.in", "pico.cpp")
+ env.copy("irq.h", "hardware/irq.h")
+ env.copy("resets.h", "hardware/resets.h")
+
+ env.substitutions = {
+ "headers": headers,
+ "defines": defines,
+ }
+ env.outbasepath = "modm/src/modm/platform"
+ env.template("device.hpp.in")
+
+
diff --git a/ext/rp/pico-sdk b/ext/rp/pico-sdk
new file mode 160000
index 0000000000..36cdca52f3
--- /dev/null
+++ b/ext/rp/pico-sdk
@@ -0,0 +1 @@
+Subproject commit 36cdca52f341713296b96c5ecbb8185fb4f596a8
diff --git a/ext/rp/pico.cpp.in b/ext/rp/pico.cpp.in
new file mode 100644
index 0000000000..8b83b36c5b
--- /dev/null
+++ b/ext/rp/pico.cpp.in
@@ -0,0 +1,31 @@
+#include "pico.h"
+#include "hardware/resets.h"
+
+#include
+%% if with_debug
+#include
+%% endif
+
+/**
+ * Pico SDK compatibility for tinyusb
+ */
+
+extern "C" void reset_block(uint32_t blocks)
+{
+ modm::platform::Resets::reset(blocks);
+}
+extern "C" void unreset_block_wait(uint32_t blocks)
+{
+ modm::platform::Resets::unresetWait(blocks);
+}
+
+extern "C" void panic(const char *fmt, ...)
+{
+%% if with_debug
+ va_list va;
+ va_start(va, fmt);
+ modm::log::info.vprintf(fmt, va);
+ va_end(va);
+%% endif
+ modm_assert(0, "pico", "Pico-SDK panic!");
+}
diff --git a/ext/rp/pico.h b/ext/rp/pico.h
new file mode 100644
index 0000000000..b7e26d9641
--- /dev/null
+++ b/ext/rp/pico.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+/**
+ * Pico SDK compatibility for tinyusb
+ */
+
+#include
+#include
+/* need for static_assert at C compilation */
+#include
+#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* override expensive assert implementation */
+#undef assert
+#define assert(X) modm_assert((X), "pico", __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #X "\"")
+void
+panic(const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/ext/rp/resets.h b/ext/rp/resets.h
new file mode 100644
index 0000000000..2a867b0278
--- /dev/null
+++ b/ext/rp/resets.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+/**
+ * Pico SDK compatibility for tinyusb
+ */
+#include "hardware/structs/resets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void
+reset_block(uint32_t blocks);
+void
+unreset_block_wait(uint32_t blocks);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/repo.lb b/repo.lb
index 0cfa12c275..3f739868a0 100644
--- a/repo.lb
+++ b/repo.lb
@@ -87,6 +87,7 @@ class DevicesCache(dict):
"stm32l0", "stm32l1", "stm32l4", "stm32l5",
"at90", "attiny", "atmega",
"samd21", "samg55", "samv70",
+ "rp2040",
"hosted"]
device_file_names = [dfn for dfn in device_file_names if any(s in dfn for s in supported)]
# These files are ignored due to various issues
@@ -178,7 +179,7 @@ class TargetOption(EnumerationOption):
targets = self._enumeration.keys()
# check if input string is part of keys OR longer
- while(len(target) > len("attiny4-")):
+ while(len(target) >= len("rp2040")):
if target in targets:
target = self._enumeration[target]
target._identifier.set("revision", revision.lower())
diff --git a/src/modm/board/rp_pico/board.hpp b/src/modm/board/rp_pico/board.hpp
new file mode 100644
index 0000000000..715828225e
--- /dev/null
+++ b/src/modm/board/rp_pico/board.hpp
@@ -0,0 +1,100 @@
+/*
+ *
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+using namespace modm::platform;
+
+namespace Board
+{
+using namespace modm::literals;
+
+/// RP2040 running at 125MHz generated from the external 12MHz crystal
+/// @ingroup modm_board_rp_pico
+struct SystemClock
+{
+ static constexpr uint32_t Frequency = 125_MHz;
+ static constexpr uint32_t XOSCFrequency = 12_MHz;
+ static constexpr uint32_t PllSysFrequency = Frequency;
+ static constexpr uint32_t PllUsbFrequency = 48_MHz;
+ static constexpr uint32_t SysPLLMul = 125;
+ static constexpr uint32_t UsbPLLMul = 40;
+ static constexpr uint32_t RefFrequency = XOSCFrequency;
+ static constexpr uint32_t UsbFrequency = PllUsbFrequency;
+ static constexpr uint32_t SysFrequency = Frequency;
+ static constexpr uint32_t PeriFrequency = SysFrequency;
+
+ static bool inline enable()
+ {
+ ClockControl::disableResus();
+ ClockControl::enableExternalCrystal(XOSCFrequency);
+ ClockControl::disableAux();
+ ClockControl::disableAux();
+ // PLL SYS: 12MHz / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz
+ ClockControl::initPll();
+ // PLL USB: 12MHz / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz
+ ClockControl::initPll();
+
+ // CLK_REF = XOSC (12MHz) / 1 = 12MHz
+ ClockControl::configureClock();
+ // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
+ ClockControl::configureClock();
+ // CLK USB = PLL USB (48MHz) / 1 = 48MHz
+ ClockControl::configureClock();
+ // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select
+ // and enable Normally choose clk_sys or clk_usb
+ ClockControl::configureClock();
+
+ ClockControl::updateCoreFrequency();
+ return true;
+ }
+};
+
+namespace usb
+{
+/// @ingroup modm_board_rp_pico
+using Device = Usb;
+}
+
+/// @ingroup modm_board_rp_pico
+/// @{
+// User LED
+using LedGreen = GpioOutput25;
+using Leds = SoftwareGpioPort;
+
+using Button = GpioUnused;
+
+inline void
+initialize()
+{
+ SystemClock::enable();
+ SysTickTimer::initialize();
+
+ LedGreen::setOutput();
+}
+
+inline void
+initializeUsbFs()
+{
+ usb::Device::initialize();
+ usb::Device::connect<>();
+}
+/// @}
+
+} // namespace Board
diff --git a/src/modm/board/rp_pico/board.xml b/src/modm/board/rp_pico/board.xml
new file mode 100644
index 0000000000..07dbffeab5
--- /dev/null
+++ b/src/modm/board/rp_pico/board.xml
@@ -0,0 +1,15 @@
+
+
+
+ ../../../../repo.lb
+
+
+
+
+
+
+
+
+ modm:board:rp-pico
+
+
diff --git a/src/modm/board/rp_pico/module.lb b/src/modm/board/rp_pico/module.lb
new file mode 100644
index 0000000000..2e3dbe5114
--- /dev/null
+++ b/src/modm/board/rp_pico/module.lb
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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 = ":board:rp-pico"
+ module.description = """\
+# Raspberry Pi Pico
+
+RP2040 Official System Development Board
+See https://www.raspberrypi.com/products/raspberry-pi-pico
+
+
+## Programming
+
+The RP2040 ships with a [UF2 bootloader in ROM](https://github.com/microsoft/uf2).
+
+To upload your application, connect the RP2040 via USB, convert the ELF to UF2
+format by calling `scons uf2` or `make uf2` and copy the generated `.uf2` file
+to the mounted virtual disk.
+"""
+
+def prepare(module, options):
+ if not options[":target"].partname.startswith("rp2040"):
+ return False
+
+ module.depends(
+ ":platform:clock",
+ ":platform:core",
+ ":platform:gpio",
+ ":platform:usb",
+ ":platform:clockgen")
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/board"
+ env.substitutions = {
+ "with_logger": False,
+ "with_assert": env.has_module(":architecture:assert")
+ }
+ env.template("../board.cpp.in", "board.cpp")
+ env.copy('.')
+
diff --git a/src/modm/platform/clock/rp/clocks.cpp b/src/modm/platform/clock/rp/clocks.cpp
new file mode 100644
index 0000000000..d40139f41c
--- /dev/null
+++ b/src/modm/platform/clock/rp/clocks.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "clocks.hpp"
+
+#include
+
+#include "../device.hpp"
+
+#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER
+#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 1
+#endif
+
+// CMSIS Core compliance
+constinit uint32_t modm_fastdata SystemCoreClock(modm::platform::ClockControl::BootFrequency);
+
+namespace modm::platform
+{
+constinit uint16_t modm_fastdata delay_fcpu_MHz(computeDelayMhz(ClockControl::BootFrequency));
+constinit uint16_t modm_fastdata delay_ns_per_loop(computeDelayNsPerLoop(ClockControl::BootFrequency));
+
+static uint32_t configured_freq[CLK_COUNT];
+
+void
+ClockControl::enableExternalCrystal(uint32_t freq)
+{
+ // Assumes 1-15 MHz input, checked above.
+ xosc_hw->ctrl = XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ;
+
+ uint32_t delay = ((((freq / 1000) + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER);
+
+ // Set xosc startup delay
+ xosc_hw->startup = delay;
+
+ // Set the enable bit now that we have set freq range and startup delay
+ hw_set_bits(&xosc_hw->ctrl, XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB);
+
+ // Wait for XOSC to be stable
+ while (!(xosc_hw->status & XOSC_STATUS_STABLE_BITS)) __NOP();
+}
+
+void
+ClockControl::configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq)
+{
+ clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)];
+ // If increasing divisor, set divisor before source. Otherwise set source
+ // before divisor. This avoids a momentary overspeed when e.g. switching
+ // to a faster source and increasing divisor to compensate.
+ if (div > clock->div) clock->div = div;
+ // If switching a glitchless slice to an aux source, switch
+ // away from aux *first* to avoid passing glitches when changing aux mux.
+ // Assume (!!!) glitchless source 0 is no faster than the aux source.
+ if (src == CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX)
+ {
+ hw_clear_bits(&clock->ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
+ while (!(clock->selected & 1u)) __NOP();
+ }
+ // If no glitchless mux, cleanly stop the clock to avoid glitches
+ // propagating when changing aux mux. Note it would be a really bad idea
+ // to do this on one of the glitchless clocks.
+ else
+ {
+ // Disable clock. On clk_ref and clk_sys this does nothing,
+ // all other clocks have the ENABLE bit in the same position.
+ hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
+ if (configured_freq[uint32_t(clk)] > 0)
+ {
+ // Delay for 3 cycles of the target clock, for ENABLE propagation.
+ // Note XOSC_COUNT is not helpful here because XOSC is not
+ // necessarily running, nor is timer... so, 3 cycles per loop:
+ uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1;
+ asm volatile(
+ ".syntax unified \n\t"
+ "1: \n\t"
+ "subs %0, #1 \n\t"
+ "bne 1b"
+ : "+r"(delay_cyc));
+ }
+ }
+
+ // Set aux mux first, and then glitchless mux
+ hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
+ CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS);
+
+ hw_write_masked(&clock->ctrl, src << CLOCKS_CLK_REF_CTRL_SRC_LSB,
+ CLOCKS_CLK_REF_CTRL_SRC_BITS);
+ while (!(clock->selected & (1u << src))) __NOP();
+
+ // Enable clock. On clk_ref and clk_sys this does nothing,
+ // all other clocks have the ENABLE bit in the same position.
+ hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
+
+ // Now that the source is configured, we can trust that the user-supplied
+ // divisor is a safe value.
+ clock->div = div;
+ // Store the configured frequency
+ configured_freq[uint32_t(clk)] = freq;
+}
+
+void
+ClockControl::configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq)
+{
+
+ clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)];
+ // If increasing divisor, set divisor before source. Otherwise set source
+ // before divisor. This avoids a momentary overspeed when e.g. switching
+ // to a faster source and increasing divisor to compensate.
+ if (div > clock->div) clock->div = div;
+ // Disable clock. On clk_ref and clk_sys this does nothing,
+ // all other clocks have the ENABLE bit in the same position.
+ hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
+ if (configured_freq[uint32_t(clk)] > 0)
+ {
+ // Delay for 3 cycles of the target clock, for ENABLE propagation.
+ // Note XOSC_COUNT is not helpful here because XOSC is not
+ // necessarily running, nor is timer... so, 3 cycles per loop:
+ uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1;
+ asm volatile(
+ ".syntax unified \n\t"
+ "1: \n\t"
+ "subs %0, #1 \n\t"
+ "bne 1b"
+ : "+r"(delay_cyc));
+ }
+
+ // Set aux mux
+ hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
+ CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS);
+
+ // Enable clock. On clk_ref and clk_sys this does nothing,
+ // all other clocks have the ENABLE bit in the same position.
+ hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
+
+ // Now that the source is configured, we can trust that the user-supplied
+ // divisor is a safe value.
+ clock->div = div;
+ // Store the configured frequency
+ configured_freq[uint32_t(clk)] = freq;
+}
+} // namespace modm::platform
diff --git a/src/modm/platform/clock/rp/clocks.hpp.in b/src/modm/platform/clock/rp/clocks.hpp.in
new file mode 100644
index 0000000000..1e3da295b7
--- /dev/null
+++ b/src/modm/platform/clock/rp/clocks.hpp.in
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+#include
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_clockgen
+class ClockControl
+{
+public:
+ enum class Clock
+ {
+%% for clk in clocks
+ {{ clk['name'] | capitalize }} = {{ clk['idx'] | int }},
+%% endfor
+ };
+ enum class ClockSrc
+ {
+%% for src in sources
+ {{ src }},
+%% endfor
+ };
+
+ enum class Pll
+ {
+ Sys,
+ Usb,
+ };
+
+ static constexpr uint32_t BootFrequency = 125'000'000; // after bootloader
+
+protected:
+ /// @cond
+ struct InvalidConfig
+ {
+ static constexpr uint32_t src = 0xffffffff;
+ static constexpr uint32_t aux_src = 0xffffffff;
+ };
+
+ template
+ struct SrcConfig : InvalidConfig
+ {};
+
+ static void
+ configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq);
+ static void
+ configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq);
+
+ static constexpr bool
+ isGlitchless(Clock clk)
+ {
+%% for clk in clocks if clk['glitchless'] == "true"
+ if (clk == Clock::{{ clk['name'] | capitalize }}) return true;
+%% endfor
+ return false;
+ }
+ /// @endcond
+
+public:
+ static inline void
+ disableResus()
+ {
+ clocks_hw->resus.ctrl = 0;
+ }
+
+ static void
+ enableExternalCrystal(uint32_t freq);
+
+ template
+ static void
+ disableAux()
+ {
+ static_assert(isGlitchless(clk),"This clock has only aux sources");
+%% for clk in clocks if clk['glitchless'] == "true"
+ {% if not loop.first %}else {% endif %}if constexpr (clk == Clock::{{ clk['name'] | capitalize }})
+ {
+ hw_clear_bits(&clocks_hw->clk[uint32_t(clk)].ctrl, CLOCKS_CLK_{{ clk['name'] | upper }}_CTRL_SRC_BITS);
+ }
+%% endfor
+ while (clocks_hw->clk[uint32_t(clk)].selected != 0x1) __NOP();
+ }
+
+ template
+ static void
+ initPll()
+ {
+ pll_hw_t* pll = pll_name == Pll::Sys ? pll_sys_hw : pll_usb_hw;
+ static_assert(pll_mul >= 16 and pll_mul <= 320, "invalid pll_mul");
+ // Check divider ranges
+ static_assert((post_div1 >= 1 and post_div1 <= 7) and (post_div2 >= 1 and post_div2 <= 7),
+ "invalid post div");
+ // post_div1 should be >= post_div2
+ // from appnote page 11
+ // postdiv1 is designed to operate with a higher input frequency
+ // than postdiv2
+ static_assert(post_div2 <= post_div1, "post_div2 vs post_div1");
+ uint32_t pdiv = (post_div1 << PLL_PRIM_POSTDIV1_LSB) | (post_div2 << PLL_PRIM_POSTDIV2_LSB);
+ if ((pll->cs & PLL_CS_LOCK_BITS) and (refdiv == (pll->cs & PLL_CS_REFDIV_BITS)) and
+ (pll_mul == (pll->fbdiv_int & PLL_FBDIV_INT_BITS)) and
+ (pdiv == (pll->prim & (PLL_PRIM_POSTDIV1_BITS & PLL_PRIM_POSTDIV2_BITS))))
+ {
+ // do not disrupt PLL that is already correctly configured and operating
+ return;
+ }
+ uint32_t pll_reset = (Pll::Usb == pll_name) ? RESETS_RESET_PLL_USB_BITS : RESETS_RESET_PLL_SYS_BITS;
+ Resets::reset(pll_reset);
+ Resets::unresetWait(pll_reset);
+
+ // Load VCO-related dividers before starting VCO
+ pll->cs = refdiv;
+ pll->fbdiv_int = pll_mul;
+
+ // Turn on PLL
+ uint32_t power = PLL_PWR_PD_BITS | // Main power
+ PLL_PWR_VCOPD_BITS; // VCO Power
+
+ hw_clear_bits(&pll->pwr, power);
+
+ // Wait for PLL to lock
+ while (!(pll->cs & PLL_CS_LOCK_BITS)) __NOP();
+
+ // Set up post dividers
+ pll->prim = pdiv;
+
+ // Turn on post divider
+ hw_clear_bits(&pll->pwr, PLL_PWR_POSTDIVPD_BITS);
+ }
+
+ /**
+ * Valid connections are:
+%% for clk in clocks
+%% for src in clk.source
+ * - Clock::{{ clk.name | capitalize }} -> ClockSrc::{{ src.cname }}
+%% endfor
+%% endfor
+ */
+ template
+ static void
+ configureClock()
+ {
+ using Config = SrcConfig;
+ constexpr uint32_t div = (uint64_t(SrcFreq) << 8) / TargetFreq;
+ static_assert(((uint64_t(SrcFreq) << 8) / div) == TargetFreq, "invalid freq");
+
+ if constexpr (isGlitchless(clk))
+ {
+ static_assert(Config::src != InvalidConfig::src and
+ Config::aux_src != InvalidConfig::aux_src,
+ "Invalid clock config");
+ configureImpl(clk, Config::src, Config::aux_src, div, TargetFreq);
+ }
+ else {
+ static_assert(Config::aux_src != InvalidConfig::aux_src, "Invalid clock config");
+ configureImpl(clk, Config::aux_src, div, TargetFreq);
+ }
+ }
+
+ template
+ static void
+ updateCoreFrequency()
+ {
+ SystemCoreClock = Core_Hz;
+ delay_fcpu_MHz = computeDelayMhz(Core_Hz);
+ delay_ns_per_loop = computeDelayNsPerLoop(Core_Hz);
+ }
+};
+
+/// @cond
+// clocks connections matrix
+%% for clk in clocks
+// {{ clk.name | capitalize }}
+%% for src in clk.source
+template<>
+struct ClockControl::SrcConfig
+{
+%% if clk.glitchless == 'true'
+ static constexpr uint32_t src = {{ src.src | int }};
+%% endif
+ static constexpr uint32_t aux_src = {{ src.aux | int }};
+};
+%% endfor
+%% endfor
+/// @endcond
+
+} // namespace modm::platform
diff --git a/src/modm/platform/clock/rp/module.lb b/src/modm/platform/clock/rp/module.lb
new file mode 100644
index 0000000000..190fd73dee
--- /dev/null
+++ b/src/modm/platform/clock/rp/module.lb
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+import re
+
+def init(module):
+ module.name = ":platform:clockgen"
+ module.description = "Clock Generator and Source"
+
+def prepare(module, options):
+ if not options[":target"].has_driver("clocks:rp*"):
+ return False
+
+ module.depends(":cmsis:device", ":utils", ":platform:clock")
+ return True
+
+def _space_numbers(match):
+ return f'{match.group(1)} {match.group(2)} {match.group(3)}'
+
+def _add_word_boundaries_to_numbers(string):
+ pattern = re.compile(r'([a-zA-Z])(\d+)([a-zA-Z]?)')
+ return pattern.sub(_space_numbers, string)
+
+def to_camel(string):
+ string = _add_word_boundaries_to_numbers(string)
+ string = string.strip(" ")
+ n = ""
+ cap_next = True
+ for v in string:
+ if (v >= 'A' and v <= 'Z') or (v >= '0' and v <= '9'):
+ n += v
+ if v >= 'a' and v <= 'z':
+ if cap_next:
+ n += v.upper()
+ else:
+ n += v
+ if v == '_' or v == ' ' or v == '-':
+ cap_next = True
+ else:
+ cap_next = False
+ return n
+
+
+def build(env):
+ device = env[":target"]
+ env.outbasepath = "modm/src/modm/platform/clock"
+ properties = {}
+ properties["target"] = device.identifier
+ clocks = device.get_driver("clocks")
+ properties["clocks"] = clocks["clock"]
+
+ sources = set()
+ for clk in clocks["clock"]:
+ for src in clk['source']:
+ src['cname'] = to_camel(src['name'])
+ sources.add(src['cname'])
+ properties['sources'] = sorted(sources)
+
+ env.substitutions = properties
+ env.copy("clocks.cpp")
+ env.template("clocks.hpp.in")
diff --git a/src/modm/platform/clock/systick/module.lb b/src/modm/platform/clock/systick/module.lb
index 6d811371ba..cb0681e145 100644
--- a/src/modm/platform/clock/systick/module.lb
+++ b/src/modm/platform/clock/systick/module.lb
@@ -46,6 +46,9 @@ def build(env):
# H742/43: Prescaler not implemented in revY
elif target.family == "h7" and target.name in ["42", "43", "50", "53"] and target.revision == "y":
div = 1
+ # RP2040 external reference clock need additional configuration
+ elif target.platform == "rp":
+ div = 1
env.substitutions = {
"systick_frequency": env.get(":freertos:frequency", freq),
diff --git a/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in b/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in
index 453a9ed70c..be4103f49b 100644
--- a/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in
+++ b/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in
@@ -16,12 +16,18 @@
#include "../device.hpp"
#include
+%% if with_multicore
+#include "multicore.hpp"
+%% endif
/// @cond
namespace modm::atomic
{
class Lock
+%% if with_multicore
+ : public modm::platform::multicore::CoreLock
+%% endif
{
public:
modm_always_inline
diff --git a/src/modm/platform/core/cortex/linker.macros b/src/modm/platform/core/cortex/linker.macros
index 20f4b57559..ea7f71769e 100644
--- a/src/modm/platform/core/cortex/linker.macros
+++ b/src/modm/platform/core/cortex/linker.macros
@@ -69,20 +69,20 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }};
%% endmacro
-%% macro section_stack(memory, start=None)
- /* Main stack in {{memory}} */
- .stack (NOLOAD) :
+%% macro section_stack(memory, start=None, suffix="")
+ /* Main stack{{suffix}} in {{memory}} */
+ .stack{{suffix}} (NOLOAD) :
{
%% if start != None
- . += {{ start }};
+ . += {{start}};
%% endif
- __stack_start = .;
+ __stack{{suffix}}_start = .;
. += MAIN_STACK_SIZE;
. = ALIGN(8);
- __main_stack_top = .;
+ __main{{suffix}}_stack_top = .;
- __stack_end = .;
+ __stack{{suffix}}_end = .;
} >{{memory}}
%% endmacro
diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb
index 2ba6d76aa7..0a25932caa 100644
--- a/src/modm/platform/core/cortex/module.lb
+++ b/src/modm/platform/core/cortex/module.lb
@@ -286,6 +286,7 @@ def build(env):
"with_memory_traits": env.has_module(":architecture:memory"),
"with_assert": env.has_module(":architecture:assert"),
"with_fpu": env.get("float-abi", "soft") != "soft",
+ "with_multicore": env.has_module(":platform:multicore"),
})
env.outbasepath = "modm/src/modm/platform/core"
diff --git a/src/modm/platform/core/cortex/module.md b/src/modm/platform/core/cortex/module.md
index e38d786f43..d0f5f021c6 100644
--- a/src/modm/platform/core/cortex/module.md
+++ b/src/modm/platform/core/cortex/module.md
@@ -142,8 +142,9 @@ The following macros are available:
- `section_load(memory, table_copy, sections)`: place each `.{section}` in
`sections` into `memory` and add them the copy table.
-- `section_stack(memory, start=None)`: place the main stack into `memory` after
- moving the location counter to `start`.
+- `section_stack(memory, start=None, suffix="")`: place the main stack into
+ `memory` after moving the location counter to `start`. `suffix` can be used
+ to add multiple `.stack{suffix}` sections.
- `section_heap(memory, name, placement=None, sections=[])`: Add the noload
`sections` to `memory` and fill up remaining space in `memory` with heap
diff --git a/src/modm/platform/core/rp/flash.ld.in b/src/modm/platform/core/rp/flash.ld.in
new file mode 100644
index 0000000000..26ff7ed33e
--- /dev/null
+++ b/src/modm/platform/core/rp/flash.ld.in
@@ -0,0 +1,102 @@
+%% import "../cortex/linker.macros" as linker with context
+{{ linker.copyright() }}
+
+{{ linker.prefix() }}
+%% set table_heap = []
+%% set table_copy = []
+%% set table_zero = []
+
+SECTIONS
+{
+{{ linker.section_rom_start("FLASH") }}
+
+ .boot2 :
+ {
+ __boot2_start__ = .;
+ KEEP (*(.boot2))
+ __boot2_end__ = .;
+ } > FLASH
+ ASSERT(__boot2_end__ - __boot2_start__ == 256,
+ "ERROR: Pico second stage bootloader must be 256 bytes in size!")
+
+ .text :
+ {
+ __vector_table_rom_start = .;
+ __vector_table_ram_load = .;
+ KEEP(*(.vector_rom))
+ __vector_table_rom_end = .;
+ } > FLASH
+
+{{ linker.section_rom("FLASH") }}
+
+
+%% if vector_table_location == "ram"
+{{ linker.section_vector_ram("RAM", table_copy) }}
+%% endif
+
+{{ linker.section_ram(cont_ram_regions[0].cont_name|upper, "FLASH", table_copy, table_zero,
+ sections_data=["fastdata", "fastcode", "data_" + cont_ram_regions[0].contains[0].name],
+ sections_bss=["bss_" + cont_ram_regions[0].contains[0].name],
+ sections_noinit=["faststack"]) }}
+
+{{ linker.all_heap_sections(table_copy, table_zero, table_heap) }}
+
+%% if with_crashcatcher
+ %#
+ /* Bottom of crash stack for `modm:platform:fault` */
+ g_crashCatcherStack = . - 500;
+ %#
+%% endif
+
+%% macro section_mem_bank(memory, section, alt_name)
+ /* Sections in {{memory}} */
+ .data_{{memory | lower}} :
+ {
+ __{{section}}_data_load = LOADADDR(.data_{{memory | lower}});
+ __{{section}}_data_start = .;
+ *(.data_{{section}} .data_{{section}}.*)
+ *(.{{alt_name}})
+ . = ALIGN(4);
+ __{{section}}_data_end = .;
+ } >{{memory}} AT > FLASH
+ %% do table_copy.append(section + "_data")
+ %#
+ .bss_{{memory | lower}} (NOLOAD) :
+ {
+ __{{section}}_bss_start = . ;
+ *(.bss_{{section}} .bss_{{section}}.*)
+ . = ALIGN(4);
+ __{{section}}_bss_end = . ;
+ } >{{memory}}
+ %% do table_zero.append(section + "_bss")
+ %#
+ .noinit_{{memory | lower}} (NOLOAD) :
+ {
+ __{{section}}_noinit_start = . ;
+ *(.noinit_{{section}} .noinit_{{section}}.*)
+ . = ALIGN(4);
+ __{{section}}_noinit_end = . ;
+ } >{{memory}}
+%% endmacro
+
+%% if with_multicore
+{{ linker.section_stack("CORE1", suffix="1") }}
+%% endif
+{{ section_mem_bank("CORE1", "core1", "scratch_x") }}
+
+
+{{ linker.section_stack("CORE0") }}
+
+{{ section_mem_bank("CORE0", "core0", "scratch_y") }}
+
+%% if linkerscript_sections
+{{ linkerscript_sections | indent(first=True) }}
+ %#
+%% endif
+
+{{ linker.section_tables("FLASH", table_copy, table_zero, table_heap) }}
+
+{{ linker.section_rom_end("FLASH") }}
+
+{{ linker.section_debug() }}
+}
diff --git a/src/modm/platform/core/rp/module.lb b/src/modm/platform/core/rp/module.lb
new file mode 100644
index 0000000000..0df0c91681
--- /dev/null
+++ b/src/modm/platform/core/rp/module.lb
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019, Ethan Slattery
+# Copyright (c) 2021, Niklas Hauser
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+import math
+
+def init(module):
+ module.name = ":platform:core"
+ module.description = FileReader("module.md")
+
+def prepare(module, options):
+ if options[":target"].identifier.platform != "rp":
+ return False
+
+ module.depends(":platform:cortex-m")
+ module.add_option(
+ EnumerationOption(
+ name="boot2",
+ description="Second-stage bootloader variant",
+ enumeration=["generic_03h", "at25sf128a", "is25lp080", "w25q080", "w25x10cl"],
+ default="generic_03h"))
+ return True
+
+
+def build(env):
+ target = env[":target"].identifier
+
+ env.substitutions = {"with_multicore": env.has_module(":platform:multicore")}
+ env.outbasepath = "modm/src/modm/platform/core"
+ # startup helper code
+ env.template("startup_platform.cpp.in")
+
+ # delay code that must be tuned for each family
+ # (cycles per loop, setup cost in loops, max CPU freq)
+ tuning = {
+ "20": (3, 4, 133000000)
+ }[target.family]
+
+ # us_shift is an optimization to limit error via fractional math
+ us_shift = 32 - math.ceil(math.log2(tuning[2]))
+
+ env.substitutions.update({
+ "with_cm0": env[":target"].has_driver("core:cortex-m0*"),
+ "with_cm7": env[":target"].has_driver("core:cortex-m7*"),
+ "loop": tuning[0],
+ "shift": int(math.log2(tuning[1])),
+ "us_shift": us_shift,
+ "with_assert": env.has_module(":architecture:assert")
+ })
+ env.template("../cortex/delay_ns.cpp.in", "delay_ns.cpp")
+ env.template("../cortex/delay_ns.hpp.in", "delay_ns.hpp")
+ env.template("../cortex/delay_impl.hpp.in", "delay_impl.hpp")
+ env.copy("resets.hpp")
+
+ env.copy(repopath("ext/rp/pico-sdk/src/boot2_{}.cpp".format(env["boot2"])),"boot2.cpp")
+
+
+def post_build(env):
+ env.substitutions = env.query("::cortex-m:linkerscript")
+ env.substitutions.update(env.query("::cortex-m:vector_table"))
+ env.substitutions["with_multicore"] = env.has_module(":platform:multicore")
+ env.outbasepath = "modm/link"
+ env.template("flash.ld.in", "linkerscript.ld")
diff --git a/src/modm/platform/core/rp/module.md b/src/modm/platform/core/rp/module.md
new file mode 100644
index 0000000000..c01905004c
--- /dev/null
+++ b/src/modm/platform/core/rp/module.md
@@ -0,0 +1,102 @@
+# RP2040 core module
+
+This module specializes the generic `modm:platform:cortex-m` module with
+RP2040-specific startup code and linkerscripts.
+
+!!! note "Second-stage Bootloader"
+ The external Flash memory requires additional setup, which is provided by
+ the second-stage bootloader called boot2. You can select a pre-defined
+ configuration via the `modm:platform:core:boot2` option.
+
+
+## Startup
+
+The `__modm_initialize_platform()` callback resets all hardware to a known
+initial state.
+
+
+## Linkerscript
+
+The RP2040 currently only has one linkerscript template for internal SRAM and
+external Flash. You can place static objects in sections via the
+`modm_section` attribute:
+
+```cpp
+// .data sections get copied from flash to RAM during startup
+modm_section(".data_core0")
+uint64_t data = 0x1234567812345678ull;
+
+// .bss sections are not stored in flash but get zeroed during startup
+modm_section(".bss_core0")
+uint8_t buffer[1024];
+
+// .noinit sections are left uninitialized
+modm_section(".noinit")
+uint8_t display_buffer[480][320];
+```
+
+
+### Dual-Core Static RAM (SRAM)
+
+The RP2040 memory map consists out of the main RAM (SRAM0-3 with striped bank
+access) and the dedicated memories for each core (SRAM4-5).
+Note that the stack size for both cores is the same and can be changed with the
+`modm:platform:cortex-m:main_stack_size` option, however, the main stack for
+CPU1 is only added if modm is build with the `modm:platform:multicore` module
+to free up the unused memory.
+
+```
+ ┌────────────────────────┐◄ __core0_end
+ │ .noinit_core0 │
+ │ .bss_core0 │
+ │ .data_core0 │
+ CORE0 │ +MAIN_STACK_SIZE │◄ __main_stack_top
+0x2004 1000 ├────────────────────────┤◄ __core1_end, __core0_start
+ │ .noinit_core1 │
+ │ .bss_core1 │
+ │ .data_core1 │
+ CORE1 │ (+MAIN_STACK_SIZE) │◄ __main1_stack_top (only with :platform:multicore)
+0x2004 0000 ├────────────────────────┤◄ __ram_end, __core1_start
+ │ +HEAP_RAM │
+ │ .noinit_ram │
+ │ .noinit │
+ │ .faststack │
+ │ .bss_ram │
+ │ .bss │
+ │ .data_ram │
+ │ .data │
+ │ .fastdata │
+ │ .fastcode │
+ RAM │ (.vector_ram) │◄ only if remapped into RAM
+0x2000 0000 └────────────────────────┘◄ __ram_start
+
+ ┌────────────────────────┐◄ __flash_end
+ │ (unused) │
+ ├────────────────────────┤◄ __rom_end
+ │ .table.heap │
+ │ .table.copy.extern │
+ tables │ .table.zero.extern │
+ │ .table.copy.intern │
+ │ .table.zero.intern │
+ │ │
+ │ .data_core1 │
+ │ .data_core0 │
+ copy │ .data_ram │
+ only │ .data │
+ │ .fastcode │
+ │ .fastdata │
+ │ │
+ │ .note.gnu.build-id │
+ │ .assertion │
+ │ .hardware_init │
+ │ (.eh_frame) │
+ read │ (.ARM.exidx) │ only with C++ exceptions enabled
+ only │ (.ARM.extab) │
+ │ .init_array │
+ │ .init │
+ │ .rodata │
+ │ .text │
+ │ .vector_rom │
+ FLASH │ .boot2 │◄ see :platform:core:boot2 option
+0x1000 0000 └────────────────────────┘◄ __rom_start, __flash_start
+```
diff --git a/src/modm/platform/core/rp/multicore.cpp b/src/modm/platform/core/rp/multicore.cpp
new file mode 100644
index 0000000000..211389345c
--- /dev/null
+++ b/src/modm/platform/core/rp/multicore.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "multicore.hpp"
+#include "../device.hpp"
+
+using namespace modm::platform::multicore;
+
+modm_weak void
+modm_initialize_core1(void)
+{}
+
+static void
+core1_wrapper(void (*entry)(void))
+{
+ modm_initialize_core1();
+ (*entry)();
+}
+
+static void __attribute__((naked)) core1_trampoline(void)
+{
+ asm volatile ("pop {r0, pc}"); // r0 = entry, pc = core1_wrapper
+}
+
+static void
+runCore1(void (*entry)(), uint32_t *stack_bottom, uint32_t stack_size)
+{
+ // assert(!(stack_size & 3u));
+ uint32_t *stack_ptr = stack_bottom + (stack_size / sizeof(uint32_t));
+ // push 1 value onto top of stack for core1_trampoline
+ stack_ptr -= 2;
+ stack_ptr[0] = (uintptr_t)entry;
+ stack_ptr[1] = (uintptr_t)core1_wrapper;
+
+ // Allow for the fact that the caller may have already enabled the FIFO IRQ for their
+ // own purposes (expecting FIFO content after core 1 is launched). We must disable
+ // the IRQ during the handshake, then restore afterwards.
+ const bool enabled = NVIC_GetEnableIRQ(SIO_IRQ_PROC0_IRQn);
+ NVIC_DisableIRQ(SIO_IRQ_PROC0_IRQn);
+
+ // Boot code start core 1 at waiting following protocol on Mailbox
+ // Values to be sent in order over the FIFO from core 0 to core 1
+ //
+ // value for VTOR register
+ // initial stack pointer (SP)
+ // initial program counter (PC) (don't forget to set the thumb bit!)
+ const uint32_t cmd_sequence[] = {
+ 0, 0, 1, (uintptr_t)SCB->VTOR, (uintptr_t)stack_ptr, (uintptr_t)core1_trampoline};
+
+ uint32_t seq = 0;
+ do {
+ uint32_t cmd = cmd_sequence[seq];
+ // Always drain the READ FIFO (from core 1) before sending a 0
+ if (!cmd)
+ {
+ Mailbox::drain();
+ // Execute a SEV as core 1 may be waiting for FIFO space via WFE
+ __SEV();
+ }
+ Mailbox::pushBlocked(cmd);
+ uint32_t response = Mailbox::popBlocked();
+ // Move to next state on correct response (echo-d value) otherwise start over
+ seq = cmd == response ? seq + 1 : 0;
+ }
+ while (seq < sizeof(cmd_sequence) / sizeof(cmd_sequence[0]));
+
+ if (enabled) NVIC_EnableIRQ(SIO_IRQ_PROC0_IRQn);
+ else NVIC_DisableIRQ(SIO_IRQ_PROC0_IRQn);
+}
+
+extern "C" uint32_t __stack1_start;
+extern "C" uint32_t __main1_stack_top;
+
+void
+Core1::run(void (*func)())
+{
+ uint32_t *stack = &__main1_stack_top;
+ runCore1(func, stack, (stack - &__stack1_start) * sizeof(uint32_t));
+}
diff --git a/src/modm/platform/core/rp/multicore.hpp.in b/src/modm/platform/core/rp/multicore.hpp.in
new file mode 100644
index 0000000000..81b2b0fbf5
--- /dev/null
+++ b/src/modm/platform/core/rp/multicore.hpp.in
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+namespace modm::platform::multicore
+{
+
+/**
+ * The RP2040 contains two FIFOs for passing data, messages or ordered events between the two cores. Each FIFO is 32 bits
+ * wide, and 8 entries deep. One of the FIFOs can only be written by core 0, and read by core 1. The other can only be written
+ * by core 1, and read by core 0.
+ * @ingroup modm_platform_multicore
+ */
+struct Mailbox
+{
+ static inline bool canRead()
+ {
+ return sio_hw->fifo_st & SIO_FIFO_ST_VLD_BITS;
+ }
+ static inline bool canWrite()
+ {
+ return sio_hw->fifo_st & SIO_FIFO_ST_RDY_BITS;
+ }
+ static inline bool push(uint32_t msg)
+ {
+ if (!canWrite()) return false;
+ sio_hw->fifo_wr = msg;
+ __SEV();
+ return true;
+ }
+ static inline bool pop(uint32_t& msg)
+ {
+ if (!canRead()) return false;
+ msg = sio_hw->fifo_rd;
+ return true;
+ }
+ static inline void pushBlocked(uint32_t msg)
+ {
+ while (!canWrite()) __NOP();
+ sio_hw->fifo_wr = msg;
+ __SEV();
+ }
+ static inline uint32_t popBlocked()
+ {
+ while (!canRead()) __WFE();
+ return sio_hw->fifo_rd;
+ }
+ static inline void drain()
+ {
+ while (canRead())
+ (void) sio_hw->fifo_rd;
+ }
+};
+
+/**
+ * The RP2040 provides 32 hardware spin locks, which can be used to manage mutually-exclusive access to shared software
+ * and hardware resources.
+ *
+ * Instance 0 allocated for modm usage.
+ *
+ * Acquire a spin lock without disabling interrupts (hence unsafe)
+ * @ingroup modm_platform_multicore
+ */
+template
+class SpinLockUnsafe
+{
+ static_assert(instance < 32);
+ using LockType = volatile uint32_t;
+
+private:
+ static inline LockType* getInstance()
+ {
+ return (LockType *) (SIO_BASE + SIO_SPINLOCK0_OFFSET + instance * 4);
+ }
+
+public:
+ static void init()
+ {
+ unlock();
+ }
+ static inline void lock()
+ {
+ auto l = getInstance();
+ while (*l == 0);
+ std::atomic_thread_fence(std::memory_order_acquire);
+ }
+ static inline void unlock()
+ {
+ auto l = getInstance();
+ std::atomic_thread_fence(std::memory_order_release);
+ *l = 0;
+ }
+};
+
+/// @cond
+// for use in modm::atomic::Lock
+struct CoreLock
+{
+ CoreLock() { SpinLockUnsafe<0>::lock(); }
+ ~CoreLock() { SpinLockUnsafe<0>::unlock(); }
+};
+/// @endcond
+
+/// This class will disable interrupts prior to acquiring the spinlock
+/// @ingroup modm_platform_multicore
+template
+class SpinLockBlocking : public SpinLockUnsafe
+{
+ using Base = SpinLockUnsafe;
+
+public:
+ static inline uint32_t lock()
+ {
+ uint32_t is = __get_PRIMASK();
+ __disable_irq();
+ Base::lock();
+ return is;
+ }
+ static inline void unlock(uint32_t is)
+ {
+ Base::unlock();
+ __set_PRIMASK(is);
+ }
+};
+/// @ingroup modm_platform_multicore
+using SystemSpinLock = SpinLockBlocking<0>;
+
+
+/// RAI lock acquiring and releasing
+/// @ingroup modm_platform_multicore
+template
+class SpinLockGuard
+{
+private:
+ SpinLockGuard(const SpinLockGuard& ) = delete;
+ SpinLockGuard(SpinLockGuard&&) = delete;
+public:
+ inline SpinLockGuard() { SpinLock::lock(); }
+ inline ~SpinLockGuard() { SpinLock::unlock(); }
+};
+
+/// @ingroup modm_platform_multicore
+template
+class SpinLockGuard>
+{
+private:
+ using SpinLock = SpinLockBlocking;
+ uint32_t is;
+ SpinLockGuard(const SpinLockGuard& ) = delete;
+ SpinLockGuard(SpinLockGuard&&) = delete;
+public:
+ inline SpinLockGuard() : is(SpinLock::lock()) {}
+ inline ~SpinLockGuard() { SpinLock::unlock(is); }
+};
+/// @ingroup modm_platform_multicore
+using SystemSpinLockGuard = SpinLockGuard;
+
+/// @ingroup modm_platform_multicore
+class Mutex
+{
+private:
+ volatile bool locked = false;
+ Mutex(const Mutex& ) = delete;
+ Mutex(Mutex&&) = delete;
+public:
+ Mutex() {}
+ void lock()
+ {
+ while(true)
+ {
+ {
+ SystemSpinLockGuard g;
+ if (!locked) {
+ locked = true;
+ return;
+ }
+ }
+ __WFE();
+ }
+ }
+ void unlock()
+ {
+ {
+ SystemSpinLockGuard g;
+ locked = false;
+ }
+ __SEV();
+ }
+};
+
+/// @ingroup modm_platform_multicore
+struct Core
+{
+ static inline uint32_t cpuId()
+ {
+ return sio_hw->cpuid;
+ }
+};
+/// Wake up (a previously reset) core 1 and enter the given function on
+/// core 1 using the default core 1 stack (below core 0 stack).
+/// @ingroup modm_platform_multicore
+struct Core1 : Core
+{
+ static void run(void(*func)());
+};
+
+} // namespace modm::platform::multicore
+
+/// @ingroup modm_platform_multicore
+/// @{
+%% for i in range(num_cores)
+/// Places initialized objects into the core{{i}}-coupled memory
+#define modm_core{{i}}_data modm_section(".data_core{{i}}")
+%% endfor
+%% for i in range(num_cores)
+/// Places zeroed objects into the core{{i}}-coupled memory
+#define modm_core{{i}}_bss modm_section(".bss_core{{i}}")
+%% endfor
+%% for i in range(num_cores)
+/// Places uninitialized objects into the core{{i}}-coupled memory
+#define modm_core{{i}}_noinit modm_section(".noinit_core{{i}}")
+%% endfor
+%% for i in range(num_cores)
+/// Places functions into the core{{i}}-coupled memory
+#define modm_core{{i}}_code modm_core{{i}}_data
+%% endfor
+/// Weak callback to initialize CPU1
+extern "C" void
+modm_initialize_core1(void);
+/// @}
diff --git a/src/modm/platform/core/rp/multicore.lb b/src/modm/platform/core/rp/multicore.lb
new file mode 100644
index 0000000000..d5eb9c4f58
--- /dev/null
+++ b/src/modm/platform/core/rp/multicore.lb
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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:multicore"
+ module.description = FileReader("multicore.md")
+
+
+def prepare(module, options):
+ if options[":target"].identifier.platform != "rp":
+ return False
+ return True
+
+
+def build(env):
+ env.outbasepath = "modm/src/modm/platform/core"
+ env.substitutions = {"num_cores": int(env[":target"].identifier.cores)}
+ env.template('multicore.hpp.in')
+ env.copy('multicore.cpp')
diff --git a/src/modm/platform/core/rp/multicore.md b/src/modm/platform/core/rp/multicore.md
new file mode 100644
index 0000000000..d73b8940ec
--- /dev/null
+++ b/src/modm/platform/core/rp/multicore.md
@@ -0,0 +1,54 @@
+# RP2040 multi-core module
+
+This module adds the ability to run code on CPU1 and provides mailbox, spinlock
+and mutex implementations, and additionally a stack of size
+`modm:platform:cortex-m:main_stack_size` is added to the CORE1 memory and the
+`modm::atomic::Lock` is made multicore safe.
+You can use attributes for placing objects into core-coupled memories:
+
+```cpp
+modm_core0_data uint8_t data = 0xab;
+modm_core1_bss uint16_t zero;
+modm_core0_noinit uint32_t uninitialized;
+
+modm_core1_code
+void function()
+{
+ data = 0xcd;
+ zero = 100;
+ uninitialized = 200;
+}
+```
+
+We provide a symmetric multiprocessing (SMP) abstraction, where the same binary
+is used for both cores. By default CPU0 is used to boot the device and code
+running on CPU1 needs to be explicitly started:
+
+```cpp
+void function()
+{
+ uint8_t cpuid = Core::cpuId();
+}
+function(); // cpuid == 0
+multicore::Core1::run(function); // cpuid == 1
+// Before CPU1 executes code, this function is called
+// so that you can customize the CPU1 environment
+extern "C" void modm_initialize_core1()
+{
+ // set a custom vector table for CPU1
+ SCB->VTOR = custom_vector_table;
+}
+```
+
+When using the `modm:processing:fiber` module you can now schedule fibers on
+both cores, each with their own scheduler:
+
+```cpp
+modm_core0_noinit modm::fiber::Stack<256> stack0;
+modm_core0_data modm::Fiber fiber0(stack0, []() { function(); }, /* core= */0);
+
+modm_core1_noinit modm::fiber::Stack<256> stack1;
+modm_core1_data modm::Fiber fiber1(stack1, []() { function(); }, /* core= */1);
+```
+
+
diff --git a/src/modm/platform/core/rp/resets.hpp b/src/modm/platform/core/rp/resets.hpp
new file mode 100644
index 0000000000..b1703d3d72
--- /dev/null
+++ b/src/modm/platform/core/rp/resets.hpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_core
+struct Resets
+{
+ static inline void
+ reset(uint32_t bits)
+ {
+ hw_set_bits(&resets_hw->reset, bits);
+ }
+ static inline void
+ unresetWait(uint32_t bits)
+ {
+ hw_clear_bits(&resets_hw->reset, bits);
+ while (~resets_hw->reset_done & bits) __NOP();
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/core/rp/startup_platform.cpp.in b/src/modm/platform/core/rp/startup_platform.cpp.in
new file mode 100644
index 0000000000..0862b1a6f0
--- /dev/null
+++ b/src/modm/platform/core/rp/startup_platform.cpp.in
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "../device.hpp"
+#include "resets.hpp"
+%% if with_multicore
+#include "multicore.hpp"
+%% endif
+
+extern "C" void
+__modm_initialize_platform(void)
+{
+%% if with_multicore
+ modm::platform::multicore::SystemSpinLock::init();
+%% endif
+
+ // Reset all peripherals to put system into a known state,
+ // - except for QSPI pads and the XIP IO bank, as this is fatal if running from flash
+ // - and the PLLs, as this is fatal if clock muxing has not been reset on this boot
+ // - and USB, syscfg, as this disturbs USB-to-SWD on core 1
+ modm::platform::Resets::reset(~(
+ RESETS_RESET_IO_QSPI_BITS |
+ RESETS_RESET_PADS_QSPI_BITS |
+ RESETS_RESET_PLL_USB_BITS |
+ RESETS_RESET_USBCTRL_BITS |
+ RESETS_RESET_SYSCFG_BITS |
+ RESETS_RESET_PLL_SYS_BITS
+ ));
+
+ // Remove reset from peripherals which are clocked only by clk_sys and
+ // clk_ref. Other peripherals stay in reset until we've configured clocks.
+ modm::platform::Resets::unresetWait(RESETS_RESET_BITS & ~(
+ RESETS_RESET_ADC_BITS |
+ RESETS_RESET_RTC_BITS |
+ RESETS_RESET_SPI0_BITS |
+ RESETS_RESET_SPI1_BITS |
+ RESETS_RESET_UART0_BITS |
+ RESETS_RESET_UART1_BITS |
+ RESETS_RESET_USBCTRL_BITS
+ ));
+
+}
diff --git a/src/modm/platform/dma/rp/dma.hpp.in b/src/modm/platform/dma/rp/dma.hpp.in
new file mode 100644
index 0000000000..e0668458d8
--- /dev/null
+++ b/src/modm/platform/dma/rp/dma.hpp.in
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "../device.hpp"
+#include
+#include
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_dma
+struct DmaBase
+{
+ using IrqHandler = void (*)(void);
+
+ enum class Channel : uint32_t
+ {
+%% for channel in dma.channel
+ Channel{{channel.name}},
+%% endfor
+ };
+ enum class Request : uint32_t
+ {
+ Pio0_Tx0 = DREQ_PIO0_TX0,
+ Pio0_Tx1 = DREQ_PIO0_TX1,
+ Pio0_Tx2 = DREQ_PIO0_TX2,
+ Pio0_Tx3 = DREQ_PIO0_TX3,
+ Pio0_Rx0 = DREQ_PIO0_RX0,
+ Pio0_Rx1 = DREQ_PIO0_RX1,
+ Pio0_Rx2 = DREQ_PIO0_RX2,
+ Pio0_Rx3 = DREQ_PIO0_RX3,
+ Pio1_Tx0 = DREQ_PIO1_TX0,
+ Pio1_Tx1 = DREQ_PIO1_TX1,
+ Pio1_Tx2 = DREQ_PIO1_TX2,
+ Pio1_Tx3 = DREQ_PIO1_TX3,
+ Pio1_Rx0 = DREQ_PIO1_RX0,
+ Pio1_Rx1 = DREQ_PIO1_RX1,
+ Pio1_Rx2 = DREQ_PIO1_RX2,
+ Pio1_Rx3 = DREQ_PIO1_RX3,
+ Spi0_Tx = DREQ_SPI0_TX,
+ Spi0_Rx = DREQ_SPI0_RX,
+ Spi1_Tx = DREQ_SPI1_TX,
+ Spi1_Rx = DREQ_SPI1_RX,
+ Uart0_Tx = DREQ_UART0_TX,
+ Uart0_Rx = DREQ_UART0_RX,
+ Uart1_Tx = DREQ_UART1_TX,
+ Uart1_Rx = DREQ_UART1_RX,
+ Pwm_Wrap0 = DREQ_PWM_WRAP0,
+ Pwm_Wrap1 = DREQ_PWM_WRAP1,
+ Pwm_Wrap2 = DREQ_PWM_WRAP2,
+ Pwm_Wrap3 = DREQ_PWM_WRAP3,
+ Pwm_Wrap4 = DREQ_PWM_WRAP4,
+ Pwm_Wrap5 = DREQ_PWM_WRAP5,
+ Pwm_Wrap6 = DREQ_PWM_WRAP6,
+ Pwm_Wrap7 = DREQ_PWM_WRAP7,
+ I2c0_Tx = DREQ_I2C0_TX,
+ I2c0_Rx = DREQ_I2C0_RX,
+ I2c1_Tx = DREQ_I2C1_TX,
+ I2c1_Rx = DREQ_I2C1_RX,
+ Adc = DREQ_ADC,
+ Xip_Stream = DREQ_XIP_STREAM,
+ Xip_SsiTx = DREQ_XIP_SSITX,
+ Xip_SsiRx = DREQ_XIP_SSIRX,
+ Dma_Timer0 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER0,
+ Dma_Timer1 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER1,
+ Dma_Timer2 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER2,
+ Dma_Timer3 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER3,
+ Force = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT,
+ };
+
+ enum class
+ TransferDataSize : uint32_t
+ {
+ Byte = 0,
+ Bit8 = Byte,
+ HalfWord = 1,
+ Bit16 = HalfWord,
+ Word = 2,
+ Bit32 = Word,
+ };
+
+ enum class
+ Priority : uint32_t
+ {
+ Normal = 0,
+ High = 1,
+ };
+};
+
+/// @author Andrey Kunitsyn
+/// @ingroup modm_platform_dma
+class DmaController : public DmaBase
+{
+public:
+ /**
+ * Start multiple channels simultaneously
+ */
+ template
+ static inline void start() {
+ dma_hw->multi_channel_trigger = (Channels::mask | ...);
+ }
+
+ class ChannelDummy
+ {
+ public:
+ static constexpr uint32_t mask = 0;
+ static void
+ configure(TransferDataSize transferDataSize,
+ bool readIncrement,
+ bool writeIncrement) {
+ (void)transferDataSize;
+ (void)readIncrement;
+ (void)writeIncrement;
+ }
+ static void
+ start() {}
+ static void
+ setReadAddress(uintptr_t address) {
+ (void)address;
+ }
+ static void
+ setWriteAddress(uintptr_t address) {
+ (void)address;
+ }
+ static void
+ setReadIncrementMode(bool increment) {
+ (void)increment;
+ }
+ static void
+ setWriteIncrementMode(bool increment) {
+ (void)increment;
+ }
+ static void
+ setDataLength(std::size_t length) {
+ (void)length;
+ }
+
+ template
+ static void
+ setPeripheralRequest() {}
+ static bool isBusy() { return false; }
+ };
+
+ /// Class representing a DMA channel/stream
+ template
+ class Channel
+ {
+ public:
+ static constexpr DmaBase::Channel name = ChannelID;
+ static constexpr uint32_t idx = uint32_t(ChannelID);
+ static constexpr uint32_t mask = (1u << idx);
+
+ /**
+ * Configure the DMA channel
+ *
+ * Stops the DMA channel and writes the new values to its control register.
+ *
+ * @param[in] transferDataSize Size of data in transfer (byte, halfword, word)
+ * @param[in] readIncrement Defines whether the read address is incremented
+ * after a transfer completed
+ * @param[in] writeIncrement Defines whether the write address is
+ * incremented after a transfer completed
+ */
+ static void
+ configure(TransferDataSize transferDataSize, bool readIncrement, bool writeIncrement)
+ {
+ const uint32_t ctrl =
+ (uint32_t(transferDataSize) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB) |
+ (readIncrement ? DMA_CH0_CTRL_TRIG_INCR_READ_BITS : 0) |
+ (writeIncrement ? DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS : 0) |
+ (idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB) |
+ DMA_CH0_CTRL_TRIG_EN_BITS |
+ DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS;
+ // write to alt1, dnt start
+ dma_hw->ch[idx].al1_ctrl = ctrl;
+ }
+
+ /// Start the transfer of the DMA channel
+ static void
+ start()
+ {
+ dma_hw->multi_channel_trigger = mask;
+ }
+
+ /// Stop a DMA channel transfer
+ static void
+ stop()
+ {
+ dma_hw->abort = mask;
+ while (dma_hw->abort & mask) __NOP();
+ }
+
+ /**
+ * Set the read address of the DMA channel
+ *
+ * @note In Mem2Mem mode use this method to set the memory source address.
+ *
+ * @param[in] address Source address
+ */
+ static void
+ setReadAddress(uintptr_t address)
+ {
+ dma_hw->ch[idx].read_addr = address;
+ }
+
+ /**
+ * Set the write address of the DMA channel
+ *
+ * @note In Mem2Mem mode use this method to set the memory destination address.
+ *
+ * @param[in] address Destination address
+ */
+ static void
+ setWriteAddress(uintptr_t address)
+ {
+ dma_hw->ch[idx].write_addr = address;
+ }
+
+ /**
+ * Enable/disable read increment
+ *
+ * When enabled, the read address is incremented by the size of the data
+ * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer
+ * completed.
+ *
+ * @param[in] increment Enable/disable
+ */
+ static void
+ setReadIncrementMode(bool increment)
+ {
+ if (increment) {
+ hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS);
+ } else {
+ hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS);
+ }
+ }
+
+ /**
+ * Enable/disable write increment
+ *
+ * When enabled, the write address is incremented by the size of the data
+ * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer
+ * completed.
+ *
+ * @param[in] increment Enable/disable
+ */
+ static void
+ setWriteIncrementMode(bool increment)
+ {
+ if (increment) {
+ hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS);
+ } else {
+ hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS);
+ }
+ }
+
+ /// Set the length of data to be transfered (num transactions, not bytes)
+ static void
+ setDataLength(std::size_t length)
+ {
+ dma_hw->ch[idx].transfer_count = length;
+ }
+
+ /// Set the peripheral that operates the channel
+ template
+ static void
+ setPeripheralRequest()
+ {
+ hw_write_masked(&dma_hw->ch[idx].al1_ctrl,
+ (uint32_t(dmaRequest) << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB),
+ DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS);
+ }
+
+ /// Enable the specified interrupt of the channel
+ static void
+ enableInterrupt()
+ {
+ hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS);
+ }
+
+ /// Disable the specified interrupt of the channel
+ static void
+ disableInterrupt()
+ {
+ hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS);
+ }
+
+ static inline bool isBusy()
+ {
+ return dma_hw->ch[idx].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS;
+ }
+
+ static inline bool isCompleted()
+ {
+ return !isBusy();
+ }
+
+ static inline void startWrite(const void* src,std::size_t count)
+ {
+ dma_hw->ch[idx].read_addr = (uint32_t)src;
+ dma_hw->ch[idx].al1_transfer_count_trig = count;
+ }
+
+ static inline void startRead(void* dst,std::size_t count)
+ {
+ dma_hw->ch[idx].write_addr = (uint32_t)dst;
+ dma_hw->ch[idx].al1_transfer_count_trig = count;
+ }
+
+ template
+ static inline void chainTo()
+ {
+ hw_write_masked(&dma_hw->ch[idx].al1_ctrl,
+ (OtherChannel::idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB),
+ DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS);
+ }
+ };
+};
+
+/**
+ * Derive DMA controller classes for convenience. Every derived class defines
+ * the channels available on that controller.
+ * @ingroup modm_platform_dma
+ */
+class Dma: public DmaController
+{
+public:
+%% for channel in dma.channel
+ using Channel{{ channel.name }} = DmaController::Channel;
+%% endfor
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/dma/rp/module.lb b/src/modm/platform/dma/rp/module.lb
new file mode 100644
index 0000000000..b5e8b5682e
--- /dev/null
+++ b/src/modm/platform/dma/rp/module.lb
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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:dma"
+ module.description = "Direct Memory Access (DMA)"
+
+
+def prepare(module, options):
+ module.depends(":cmsis:device", ":platform:clockgen")
+ return options[":target"].has_driver("dma:rp20*")
+
+
+def build(env):
+ env.substitutions = {"dma": env[":target"].get_driver("dma")}
+ env.outbasepath = "modm/src/modm/platform/dma"
+ env.template("dma.hpp.in")
+
diff --git a/src/modm/platform/gpio/common/connector.hpp.in b/src/modm/platform/gpio/common/connector.hpp.in
index 1fb4dcbd41..0ee46e9c1a 100644
--- a/src/modm/platform/gpio/common/connector.hpp.in
+++ b/src/modm/platform/gpio/common/connector.hpp.in
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2017, 2021, Niklas Hauser
+ * Copyright (c) 2022, Andrey Kunitsyn
*
* This file is part of the modm project.
*
@@ -77,6 +78,15 @@ struct GpioConnector
%% endif
%% elif target.platform in ["avr"]
static Connection check [[maybe_unused]];
+%% elif target.platform in ["rp"]
+ using Pin = GpioStatic;
+ if constexpr(Connection::func == -1) {
+ Pin::setInput();
+ Pin::disconnect();
+ }
+ if constexpr (Connection::func >= 0) {
+ Pin::setFunction(Connection::func);
+ }
%% endif
}
diff --git a/src/modm/platform/gpio/common/pins.hpp.in b/src/modm/platform/gpio/common/pins.hpp.in
index 8547ba4c25..34d72b61fb 100644
--- a/src/modm/platform/gpio/common/pins.hpp.in
+++ b/src/modm/platform/gpio/common/pins.hpp.in
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Niklas Hauser
+ * Copyright (c) 2022, Andrey Kunitsyn
*
* This file is part of the modm project.
*
@@ -20,11 +21,14 @@ namespace modm::platform
/// @ingroup modm_platform_gpio
/// @{
%% for gpio in gpios
-%% set port = gpio.gpio.port | upper
-%% set pin = gpio.gpio.pin
-using Gpio{{ port ~ pin }} = GpioStatic;
-using GpioOutput{{ port ~ pin }} = Gpio{{ port ~ pin }};
-using GpioInput{{ port ~ pin }} = Gpio{{ port ~ pin }};
+%% if target.platform in ["rp"]
+%% set name = gpio.gpio.name | capitalize
+%% else
+%% set name = gpio.gpio.port | upper ~ gpio.gpio.pin
+%% endif
+using Gpio{{ name }} = GpioStatic;
+using GpioOutput{{ name }} = Gpio{{ name }};
+using GpioInput{{ name }} = Gpio{{ name }};
%#
%% endfor
/// @}
diff --git a/src/modm/platform/gpio/common/unused.hpp.in b/src/modm/platform/gpio/common/unused.hpp.in
index fdf2c93372..80c861aa62 100644
--- a/src/modm/platform/gpio/common/unused.hpp.in
+++ b/src/modm/platform/gpio/common/unused.hpp.in
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2017-2018, 2021, Niklas Hauser
+ * Copyright (c) 2022, Andrey Kunitsyn
*
* This file is part of the modm project.
*
@@ -70,6 +71,9 @@ protected:
static void setAlternateFunction(uint8_t) {}
static void setAlternateFunction() {}
static void setAnalogInput() {}
+%% elif target.platform in ["rp"]
+ static void setFunction(uint8_t) {}
+ static void setDriveStrength(DriveStrength) {}
%% endif
public:
// GpioOutput
@@ -108,7 +112,7 @@ public:
using Data = detail::DataUnused;
static constexpr platform::Gpio::Signal Signal = platform::Gpio::Signal::BitBang;
};
-%% for name in all_signals
+%% for name in all_signals | sort
struct {{ name }}
{
using Data = detail::DataUnused;
diff --git a/src/modm/platform/gpio/rp/base.hpp.in b/src/modm/platform/gpio/rp/base.hpp.in
new file mode 100644
index 0000000000..809e32751c
--- /dev/null
+++ b/src/modm/platform/gpio/rp/base.hpp.in
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ * Copyright (c) 2022, Niklas Hauser
+ *
+ * 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
+%% for port in ports
+#include
+#include
+%% endfor
+#include
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_gpio
+struct Gpio
+{
+ enum class
+ InputType
+ {
+ Floating = 0x0, ///< floating on input
+ PullUp = 0x1, ///< pull-up on input
+ PullDown = 0x2, ///< pull-down on input
+ };
+
+ enum class
+ OutputType
+ {
+ PushPull
+ };
+
+ enum class
+ OutputSpeed
+ {
+ Slow = 0,
+ Fast = 1,
+ };
+ enum class
+ DriveStrength
+ {
+ mA_2 = 0,
+ mA_4 = 1,
+ mA_8 = 2,
+ mA_12 = 3,
+ };
+
+ enum class
+ Port
+ {
+%% for port in ports
+ {{ port }},
+%% endfor
+ };
+
+ /// @cond
+ enum class
+ Signal
+ {
+ BitBang,
+%% for signal in all_signals | sort
+ {{ signal }},
+%% endfor
+ };
+
+ template
+ struct PortRegs;
+ /// @endcond
+};
+
+/// @cond
+%% for port in ports
+template <>
+struct Gpio::PortRegs
+{
+ using status_ctrl_hw_t = io{{port | lower}}_status_ctrl_hw_t;
+ static uint32_t sio_in() { return sio_hw->gpio{{sio_names[port]}}_in; }
+ static uint32_t sio_out() { return sio_hw->gpio{{sio_names[port]}}_out; }
+ static uint32_t sio_oe() { return sio_hw->gpio{{sio_names[port]}}_oe; }
+ static void sio_set(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_set = mask; }
+ static void sio_clr(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_clr = mask; }
+ static void sio_togl(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_togl = mask; }
+ static void sio_oe_set(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_oe_set = mask; }
+ static void sio_oe_clr(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_oe_clr = mask; }
+ static uint8_t funcsel(uint8_t pin)
+ { return (io{{port | lower}}_hw->io[pin].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; }
+ static void set_funcsel(uint8_t pin, uint32_t fn)
+ {
+ hw_write_masked(&pads{{(pads_names[port] or port) | lower}}_hw->io[pin],
+ PADS_BANK0_GPIO0_IE_BITS,
+ PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS
+ );
+ io{{port | lower}}_hw->io[pin].ctrl = fn << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB;
+ }
+ static void set_pue_pde(uint8_t pin, bool up, bool down)
+ {
+ hw_write_masked(
+ &pads{{(pads_names[port] or port) | lower}}_hw->io[pin],
+ ((up?1:0) << PADS_BANK0_GPIO0_PUE_LSB) | ((down?1:0) << PADS_BANK0_GPIO0_PDE_LSB),
+ PADS_BANK0_GPIO0_PUE_BITS | PADS_BANK0_GPIO0_PDE_BITS
+ );
+ }
+ static void set_drive(uint8_t pin, uint8_t strength)
+ {
+ hw_write_masked(
+ &pads{{(pads_names[port] or port) | lower}}_hw->io[pin],
+ strength << PADS_BANK0_GPIO0_DRIVE_LSB,
+ PADS_BANK0_GPIO0_DRIVE_BITS
+ );
+ }
+ static void set_slewfast(uint8_t pin, bool fast)
+ {
+ hw_write_masked(
+ &pads{{(pads_names[port] or port) | lower}}_hw->io[pin],
+ (fast?1:0) << PADS_BANK0_GPIO0_SLEWFAST_LSB,
+ PADS_BANK0_GPIO0_SLEWFAST_BITS
+ );
+ }
+};
+%% endfor
+/// @endcond
+
+} // namespace modm::platform
diff --git a/src/modm/platform/gpio/rp/data.hpp.in b/src/modm/platform/gpio/rp/data.hpp.in
new file mode 100644
index 0000000000..3212997811
--- /dev/null
+++ b/src/modm/platform/gpio/rp/data.hpp.in
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ * Copyright (c) 2022, Niklas Hauser
+ *
+ * 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 "base.hpp"
+
+/// @cond
+namespace modm::platform::detail
+{
+
+template struct SignalConnection;
+
+struct DataUnused {};
+%% for gpio in gpios
+%% set name = gpio.gpio.name | capitalize
+%#
+struct Data{{ name }}
+{
+ static constexpr Gpio::Port port = Gpio::Port::{{ gpio.gpio.port | capitalize }};
+ static constexpr uint8_t pin = {{ gpio.gpio.pin }};
+ struct BitBang { using Data = Data{{ name }}; static constexpr Gpio::Signal Signal = Gpio::Signal::BitBang; };
+%% for signal in gpio.signals
+ struct {{ signal }} { using Data = Data{{ name }}; static constexpr Gpio::Signal Signal = Gpio::Signal::{{ signal }}; };
+%% endfor
+};
+template struct SignalConnection {
+ static_assert(p == Peripheral::BitBang, "Gpio{{ name }}::BitBang only connects to software drivers!"); };
+%% for signal_name, signal_group in gpio.signals.items()
+template struct SignalConnection {
+ static_assert((p == Peripheral::{{ signal_group | map(attribute="driver") | join(") or (p == Peripheral::") }}),{% if signal_group | length > 1 %}
+ {% endif %}"Gpio{{ name }}::{{ signal_name }} only connects to {{ signal_group | map(attribute="driver") | join(" or ") }}!"); };
+%% endfor
+template<> struct SignalConnection { static constexpr int8_t func = -1; };
+%% for signal_group in gpio.signals.values()
+ %% for signal in signal_group
+template<> struct SignalConnection { static constexpr int8_t func = {{ signal.af }}; };
+ %% endfor
+%% endfor
+%% endfor
+
+} // namespace modm::platform::detail
+/// @endcond
diff --git a/src/modm/platform/gpio/rp/module.lb b/src/modm/platform/gpio/rp/module.lb
new file mode 100644
index 0000000000..6ae1fa611b
--- /dev/null
+++ b/src/modm/platform/gpio/rp/module.lb
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+# Copyright (c) 2022, Niklas Hauser
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+import copy
+from collections import defaultdict, OrderedDict
+
+def port_ranges(gpios):
+ ports = {p: (32, 0) for p in set(p["port"] for p in gpios)}
+ for gpio in gpios:
+ pin = int(gpio["pin"])
+ pmin, pmax = ports[gpio["port"]]
+ ports[gpio["port"]] = (min(pin, pmin), max(pin, pmax))
+
+ ports = [{"name": k.capitalize(), "start": v[0], "width": v[1] - v[0] + 1} for k,v in ports.items()]
+ ports.sort(key=lambda p: p["name"])
+ return ports
+
+def translate(s):
+ return "".join(p.capitalize() for p in s.split("_"))
+# -----------------------------------------------------------------------------
+
+
+def init(module):
+ module.name = ":platform:gpio"
+ module.description = "General Purpose I/O (GPIO)"
+
+
+def prepare(module, options):
+ module.depends(":architecture:gpio", ":cmsis:device", ":math:utils")
+ return options[":target"].has_driver("gpio:rp*")
+
+
+def build(env):
+ device = env[":target"]
+ driver = device.get_driver("gpio")
+
+ subs = {
+ "ranges": port_ranges(driver["gpio"]),
+ "sio_names": {
+ "Bank0": "",
+ "Qspi": "_hi"
+ },
+ "pads_names": {
+ "Bank0": "bank0",
+ "Qspi": "_qspi"
+ },
+ "port_width": 32
+ }
+ subs["ports"] = OrderedDict([(k, i) for i, k in enumerate([p["name"].capitalize() for p in subs["ranges"]])])
+
+ peripherals = []
+ all_drivers = [d for d in device._properties["driver"] if d["name"] not in ["gpio", "core"]]
+ for d in all_drivers:
+ dname = translate(d["name"])
+ if "instance" in d:
+ peripherals.extend([dname + translate(i) for i in d["instance"]])
+ else:
+ peripherals.append(dname)
+ subs["all_peripherals"] = sorted(list(set(peripherals)))
+
+ all_signals = set()
+ for gpio in driver["gpio"]:
+ gpio["signals"] = set([translate(s["name"]) for s in gpio["signal"]])
+ all_signals.update(gpio["signals"])
+ signals = defaultdict(list)
+ for sig in gpio["signal"]:
+ signals[translate(sig["name"])].append(
+ {"driver": sig["driver"].capitalize() + sig.get("instance", ""),
+ "name": translate(sig["name"]),
+ "af": sig["af"]})
+ subs[gpio["name"].capitalize()] = {
+ "gpio": gpio,
+ "signals": signals,
+ }
+
+ subs["target"] = device.identifier
+ subs["all_signals"] = all_signals
+ subs["gpios"] = [subs[gpio["name"].capitalize()] for gpio in driver["gpio"]]
+
+ env.substitutions = subs
+ env.outbasepath = "modm/src/modm/platform/gpio"
+
+ env.template("base.hpp.in")
+ env.template("data.hpp.in")
+ env.template("static.hpp.in")
+ env.template("set.hpp.in")
+ env.template("software_port.hpp.in")
+ env.template("port.hpp.in")
+
+ env.copy("../common/inverted.hpp", "inverted.hpp")
+ env.copy("../common/open_drain.hpp", "open_drain.hpp")
+ env.template("../common/connector.hpp.in", "connector.hpp",
+ filters={"formatPeripheral": "", "printSignalMap": ""})
+ env.template("../common/unused.hpp.in", "unused.hpp")
+ env.template("../common/pins.hpp.in", "pins.hpp")
+ env.template("../common/port_shim.hpp.in", "port_shim.hpp")
+
+ # FIXME: Move to modm:platform:core!
+ env.outbasepath = "modm/src/modm/platform/core"
+ env.template("peripherals.hpp.in")
diff --git a/src/modm/platform/gpio/rp/module.md b/src/modm/platform/gpio/rp/module.md
new file mode 100644
index 0000000000..b0f1c9a158
--- /dev/null
+++ b/src/modm/platform/gpio/rp/module.md
@@ -0,0 +1,125 @@
+# General Purpose I/O (GPIO)
+
+This module provides register access to GPIO and connect their signals to the
+respective peripherals in a compile-time verified way.
+
+Each GPIO is represented as its own class with only static methods, which
+implement the `modm::GpioIO` interface and provide additional platform-specific
+methods.
+
+```cpp
+using namespace modm::platform;
+
+using Button = GpioInput0;
+Button::setInput(Gpio::InputType::PullUp);
+bool input = Button::read();
+
+using Led = GpioInverted; // inverts the IO logic of the pin
+
+Led::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::Fast);
+Led::set(input);
+
+using Signal = Gpio4;
+Signal::setFunction(4); // For func id see datasheet
+Signal::disconnect(); // Switch back to floating input
+
+using OpenDrain = GpioOpenDrain;
+OpenDrain::setInput(Gpio::InputType::PullUp);
+OpenDrain::set(); // Input with Pullup
+OpenDrain::reset(); // Output with PushPull Low
+```
+
+You can also use an unordered set of GPIOs, which is useful when configuring a
+large number of pins, since the register accesses will be bundled and thus less
+code is generated.
+
+```cpp
+using Set = GpioSet;
+Set::setInput();
+```
+
+To write and read a set of GPIOs, you need to use an ordered implementation,
+which defines the pins from MSB to LSB, left-to-right. You can also check the
+number of ports in case your use-case requires atomic reads/writes.
+
+```cpp
+using Port = SoftwareGpioPort;
+static_assert(Port::number_of_ports == 1, "Read/write needs to be atomic");
+Port::setInput(Gpio::InputType::PullUp);
+uint8_t nibble = Port::read();
+Port::setOutput();
+Port::write(nibble);
+```
+
+For efficient access you can use a strictly-ordered implementation with a start
+pin and width. Note that you can reverse the data order with a negative width.
+
+```cpp
+using Port = GpioPort;
+Port::setOutput();
+Port::write(data);
+
+using ReversePort = GpioPort;
+ReversePort::setInput();
+uint8_t data = ReversePort::read();
+```
+
+Finally, you can use an empty GPIO implementation in cases where the API
+requires a GPIO, but you don't need one, for example, a bit-banged SPI without
+MISO pin:
+
+```cpp
+// write only SPI
+using SPI = modm::platform::BitBangSpiMaster;
+```
+
+
+## Function Signals
+
+To make it easier to connect pins with peripherals, this module implements a
+compile-time map of (pin, signal, peripheral) to Function ID (func).
+Note that you must provide both peripherals and signals to be unambiguous.
+
+```cpp
+GpioConnector::connect();
+```
+
+However, it is recommended to wrap this functionality into a separate function
+`Driver::connect(config)`, so that additional driver specific pin
+configuration can be done:
+
+```cpp
+template< class... Signals >
+void Uart1::connect()
+{
+ Connector = GpioConnector;
+ Connector::disconnect(); // reset to floating input
+
+ // extract pins from signals
+ using Rx = Connector::GetSignal;
+ using Tx = Connector::GetSignal;
+ // if not found, returns GpioUnused, you can check for this case
+ static_assert(not Connector::isValid,
+ "This UART driver requires the Tx signal");
+
+ // configure both pins
+ Rx::configure(Gpio::InputType::PullUp);
+ Tx::configure(Gpio::OutputType::PushPull);
+
+ // connect both pins to alternate functions
+ // This will static assert if signals do not make sense
+ Connector::connect();
+}
+// Connect these pin signals to Uart1
+Uart1::connect();
+```
+
+Note that you may pass a *variable* number of signals to this connect function,
+leaving out signals you don't need and adding signals that are not required.
+
+```cpp
+// Connect only one signal
+Uart1::connect();
+// Connect more signals than required
+Uart1::connect();
+```
diff --git a/src/modm/platform/gpio/rp/peripherals.hpp.in b/src/modm/platform/gpio/rp/peripherals.hpp.in
new file mode 100644
index 0000000000..2368486d9f
--- /dev/null
+++ b/src/modm/platform/gpio/rp/peripherals.hpp.in
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_core
+enum class
+Peripheral
+{
+ BitBang,
+%% for per in all_peripherals
+ {{ per }},
+%% endfor
+};
+
+}
diff --git a/src/modm/platform/gpio/rp/port.hpp.in b/src/modm/platform/gpio/rp/port.hpp.in
new file mode 100644
index 0000000000..9396be8997
--- /dev/null
+++ b/src/modm/platform/gpio/rp/port.hpp.in
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2016-2018, 2022, Niklas Hauser
+ *
+ * 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 "port_shim.hpp"
+#include "set.hpp"
+#include
+#include
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_gpio
+template< class StartGpio, int8_t Width >
+class GpioPort : public ::modm::GpioPort/** @cond */, public detail::GpioSetShim /** @endcond */
+{
+ using PinSet = detail::GpioSetShim;
+public:
+ using PinSet::width;
+ static_assert(width <= 16, "Only a maximum of 16 pins are supported by GpioPort!");
+ using PortType = std::conditional_t< (width > 8), uint16_t, uint8_t >;
+ static constexpr DataOrder getDataOrder()
+ { return Width > 0 ? GpioPort::DataOrder::Normal : GpioPort::DataOrder::Reversed; }
+
+protected:
+ using PinSet::mask;
+ using PinSet::inverted;
+ static constexpr uint8_t StartPin = Width > 0 ? StartGpio::pin : StartGpio::pin - width + 1;
+ static constexpr uint8_t StartPinReversed = (8 - StartPin - width) + 8;
+
+public:
+ static PortType isSet()
+ {
+ uint16_t r{0};
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) r = (Gpio::PortRegs::sio_out() & mask({{id}})) ^ inverted({{id}});
+%% endfor
+ if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed)
+ return bitReverse(r) >> StartPinReversed;
+ else return r >> StartPin;
+ }
+
+ static PortType read()
+ {
+ uint16_t r{0};
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) r = (Gpio::PortRegs::sio_in() & mask({{id}})) ^ inverted({{id}});
+%% endfor
+ if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed)
+ return bitReverse(r) >> StartPinReversed;
+ else return r >> StartPin;
+ }
+
+ static void write(PortType data)
+ {
+ uint16_t p;
+ if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed)
+ p = bitReverse(uint16_t(uint16_t(data) << StartPinReversed));
+ else p = uint16_t(data) << StartPin;
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) {
+ p ^= inverted({{id}});
+ Gpio::PortRegs::sio_set(p);
+ Gpio::PortRegs::sio_clr(~p & mask({{id}}));
+ }
+%% endfor
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/gpio/rp/set.hpp.in b/src/modm/platform/gpio/rp/set.hpp.in
new file mode 100644
index 0000000000..52f895b8c9
--- /dev/null
+++ b/src/modm/platform/gpio/rp/set.hpp.in
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ * Copyright (c) 2022, Niklas Hauser
+ *
+ * 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"
+#include "base.hpp"
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_gpio
+template< class... Gpios >
+class GpioSet : public Gpio
+{
+ static constexpr uint32_t inverteds[{{ ports | length }}] = {
+%% for port in ports
+ (((Gpios::port == Port::{{port}} and Gpios::isInverted) ? Gpios::mask : 0) | ...),
+%% endfor
+ };
+
+ static constexpr uint32_t masks[{{ ports | length }}] = {
+%% for port in ports
+ (((Gpios::port == Port::{{port}}) ? Gpios::mask : 0) | ...),
+%% endfor
+ };
+ static constexpr uint8_t numberOfPorts() {
+ uint8_t r{0};
+ for (const auto &m: masks) r += (m) ? 1 : 0;
+ return r;
+ }
+
+protected:
+ static constexpr uint32_t inverted(uint8_t id) { return inverteds[id]; }
+ static constexpr uint32_t mask(uint8_t id) { return masks[id]; }
+
+public:
+ static constexpr uint8_t width = sizeof...(Gpios);
+ static constexpr uint8_t number_of_ports = numberOfPorts();
+
+public:
+ static void setOutput()
+ {
+ disconnect();
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) PortRegs::sio_oe_set(mask({{id}}));
+%% endfor
+ }
+
+ static void setOutput(bool status)
+ {
+ set(status);
+ setOutput();
+ }
+
+ static void setOutput(OutputType type, OutputSpeed speed = OutputSpeed::Slow)
+ {
+ setOutput();
+ configure(type, speed);
+ }
+
+ static void configure(OutputType, OutputSpeed speed = OutputSpeed::Slow)
+ {
+ (PortRegs::set_speed(Gpios::pin, speed), ...);
+ }
+
+ static void setInput()
+ {
+ setInput(InputType::Floating);
+ }
+
+ static void setInput(InputType type)
+ {
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) PortRegs::sio_oe_clr(mask({{id}}));
+%% endfor
+ configure(type);
+ }
+
+ static void configure(InputType type)
+ {
+ (PortRegs::set_pue_pde(Gpios::pin, type==InputType::PullUp, type==InputType::PullDown), ...);
+ }
+
+ static void set()
+ {
+%% for port, id in ports.items()
+ if constexpr (mask({{id}}) & ~inverted({{id}}))
+ PortRegs::sio_set(mask({{id}}) & ~inverted({{id}}));
+ if constexpr (mask({{id}}) & inverted({{id}}))
+ PortRegs::sio_clr(mask({{id}}) & inverted({{id}}));
+%% endfor
+ }
+
+ static void set(bool status)
+ {
+ if (status) set();
+ else reset();
+ }
+
+ static void reset()
+ {
+%% for port, id in ports.items()
+ if constexpr (mask({{id}}) & inverted({{id}}))
+ PortRegs::sio_set(mask({{id}}) & inverted({{id}}));
+ if constexpr (mask({{id}}) & ~inverted({{id}}))
+ PortRegs::sio_clr(mask({{id}}) & ~inverted({{id}}));
+%% endfor
+ }
+
+ static void toggle()
+ {
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) PortRegs::sio_togl(mask({{id}}));
+%% endfor
+ }
+
+ static void disconnect()
+ {
+ (Gpios::disconnect(), ...);
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/gpio/rp/software_port.hpp.in b/src/modm/platform/gpio/rp/software_port.hpp.in
new file mode 100644
index 0000000000..8699e620e4
--- /dev/null
+++ b/src/modm/platform/gpio/rp/software_port.hpp.in
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018, 2022, Niklas Hauser
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "set.hpp"
+#include
+
+namespace modm::platform
+{
+
+/**
+ * Create an up to 32-bit port from arbitrary pins.
+ *
+ * This class optimizes the data type for the `read()` and `write()` methods.
+ * Supplying up to 8 Gpios will use `uint8_t`, up to 16 Gpios `uint16_t` and
+ * up to 32 Gpios `uint32_t`.
+ *
+ * @note Since the bit order is explicitly given by the order of arguments,
+ * this class only supports `DataOrder::Normal`.
+ * If you need reverse bit order, reverse the order of `Gpios`!
+ *
+ * @tparam Gpios Up to 32 GpioIO classes, ordered MSB to LSB
+ *
+ * @author Niklas Hauser
+ * @ingroup modm_platform_gpio
+ */
+template< class... Gpios >
+class SoftwareGpioPort : public ::modm::GpioPort, public GpioSet
+{
+ using Set = GpioSet;
+public:
+ using Set::width;
+ static_assert(width <= 32, "Only a maximum of 32 pins are supported by this Port!");
+ using PortType = std::conditional_t< (width > 8),
+ std::conditional_t< (width > 16),
+ uint32_t,
+ uint16_t >,
+ uint8_t >;
+ static constexpr DataOrder getDataOrder()
+ { return ::modm::GpioPort::DataOrder::Normal; }
+
+protected:
+ static constexpr int8_t shift_masks[{{ ports | length }}][width] = {
+%% for port in ports
+ {(Gpios::port == Gpio::Port::{{port}} ? Gpios::pin : -1)...},
+%% endfor
+ };
+ static constexpr int8_t shift_mask(uint8_t id, uint8_t pos) { return shift_masks[id][width - 1 - pos]; }
+ using Set::mask;
+ using Set::inverted;
+
+public:
+ static PortType isSet()
+ {
+ PortType r{0};
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) {
+ const uint32_t p = (Gpio::PortRegs::sio_out() & mask({{id}})) ^ inverted({{id}});
+ %% for pos in range(32)
+ if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) r |= ((p >> pos) & 1) << {{pos}};
+ %% endfor
+ }
+%% endfor
+ return r;
+ }
+
+ static void write(PortType data)
+ {
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) { uint32_t ps{0}; uint32_t pr{0};
+ %% for pos in range(32)
+ if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) { if (data & (1ul << {{pos}})) { ps |= (1ul << pos); } else { pr |= (1ul << pos); }}
+ %% endfor
+ ps ^= inverted({{id}});
+ pr ^= inverted({{id}});
+ if (ps) Gpio::PortRegs::sio_set(ps);
+ if (pr) Gpio::PortRegs::sio_clr(pr);
+ }
+%% endfor
+ }
+
+ static PortType read()
+ {
+ PortType r{0};
+%% for port, id in ports.items()
+ if constexpr (mask({{id}})) {
+ const uint32_t p = (Gpio::PortRegs::sio_in() & mask({{id}})) ^ inverted({{id}});
+ %% for pos in range(32)
+ if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) r |= ((p >> pos) & 1) << {{pos}};
+ %% endfor
+ }
+%% endfor
+ return r;
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/gpio/rp/static.hpp.in b/src/modm/platform/gpio/rp/static.hpp.in
new file mode 100644
index 0000000000..aa274ec2e0
--- /dev/null
+++ b/src/modm/platform/gpio/rp/static.hpp.in
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ * Copyright (c) 2022, Niklas Hauser
+ *
+ * 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 "../device.hpp"
+#include "set.hpp"
+#include
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_gpio
+template
+class GpioStatic : public Gpio, public GpioData
+{
+ template friend class GpioSet;
+ using PinSet = GpioSet>;
+ using Regs = PortRegs;
+ static constexpr uint8_t SioFunc = 5;
+
+public:
+ using Direction = ::modm::Gpio::Direction;
+ using Type = GpioStatic;
+ using Output = Type;
+ using Input = Type;
+ using IO = Type;
+ using Data = GpioData;
+ using GpioData::port;
+ using GpioData::pin;
+ static constexpr Direction direction = Direction::InOut;
+ static constexpr bool isInverted = false;
+ static constexpr uint32_t mask = 1lu << pin;
+
+ static void setFunction(uint8_t func) { Regs::set_funcsel(pin, func); }
+ static void setDriveStrength(DriveStrength strength)
+ { Regs::set_drive(pin, strength); }
+
+public:
+ static void setOutput() { PinSet::setOutput(); }
+ static void setOutput(bool value) { PinSet::setOutput(value); }
+ static void setOutput(OutputType type, OutputSpeed speed=OutputSpeed::Slow)
+ { PinSet::setOutput(type, speed); }
+ static void configure(OutputType type, OutputSpeed speed=OutputSpeed::Slow)
+ { PinSet::configure(type, speed); }
+
+ static void set() { PinSet::set(); }
+ static void set(bool status) { PinSet::set(status); }
+ static void reset() { PinSet::reset(); }
+ static bool isSet() { return Regs::sio_out() & mask; }
+ static void toggle() { PinSet::toggle(); }
+
+ static void setInput() { PinSet::setInput(); }
+ static void setInput(InputType type) { PinSet::setInput(type); }
+ static void configure(InputType type) { PinSet::configure(type); }
+
+ static bool read() { return Regs::sio_in() & mask; }
+
+ static Direction getDirection()
+ {
+ if (Regs::funcsel(pin) != SioFunc) return Direction::Special;
+ return (Regs::sio_oe() & mask) ? Direction::Out : Direction::In;
+ }
+ static void disconnect() { setFunction(SioFunc); }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in b/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in
index 50f987cc97..9027020e94 100644
--- a/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in
+++ b/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in
@@ -20,7 +20,7 @@
#include
#include
#include
-%% if target.platform in ["avr"]
+%% if target.platform in ["avr", "rp"]
#include
%% endif
@@ -41,7 +41,7 @@ template< class Scl,
class Sda >
class BitBangI2cMaster : public modm::I2cMaster
{
-%% if target.platform in ["avr"]
+%% if target.platform in ["avr", "rp"]
using SCL = platform::GpioOpenDrain;
using SDA = platform::GpioOpenDrain;
%% else
diff --git a/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in b/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in
index 0846eab5ac..eb64a4c591 100644
--- a/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in
+++ b/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in
@@ -17,7 +17,7 @@
#include
#include
#include
-%% if target.platform in ["avr"]
+%% if target.platform in ["avr", "rp"]
#include
%% endif
@@ -48,7 +48,7 @@ namespace platform
template
class BitBangOneWireMaster
{
-%% if target.platform in ["avr"]
+%% if target.platform in ["avr", "rp"]
using PIN = platform::GpioOpenDrain;
%% else
using PIN = Pin;
diff --git a/src/modm/platform/spi/rp/module.lb b/src/modm/platform/spi/rp/module.lb
new file mode 100644
index 0000000000..089f9de88a
--- /dev/null
+++ b/src/modm/platform/spi/rp/module.lb
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+class Instance(Module):
+ def __init__(self, driver, instance):
+ self.instance = int(instance)
+ self.driver = driver
+
+ 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):
+ env.substitutions = {"id": self.instance}
+ env.outbasepath = "modm/src/modm/platform/spi"
+
+ env.template("spi_master.hpp.in", "spi_master_{}.hpp".format(self.instance))
+ env.template("spi_master.cpp.in", "spi_master_{}.cpp".format(self.instance))
+
+ if env.has_module(":platform:dma"):
+ env.template("spi_master_dma.hpp.in", "spi_master_{}_dma.hpp".format(self.instance))
+ env.template("spi_master_dma_impl.hpp.in", "spi_master_{}_dma_impl.hpp".format(self.instance))
+
+
+def init(module):
+ module.name = ":platform:spi"
+ module.description = "Serial Peripheral Interface (SPI)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("spi:rp20*"):
+ return False
+
+ module.depends(
+ ":architecture:spi",
+ ":cmsis:device",
+ ":math:algorithm",
+ ":platform:gpio",
+ ":platform:clockgen")
+
+ for driver in device.get_all_drivers("spi:rp20*"):
+ for instance in driver["instance"]:
+ module.add_submodule(Instance(driver, instance))
+
+ return True
+
+def build(env):
+ pass
diff --git a/src/modm/platform/spi/rp/spi_master.cpp.in b/src/modm/platform/spi/rp/spi_master.cpp.in
new file mode 100644
index 0000000000..7385a6ff9e
--- /dev/null
+++ b/src/modm/platform/spi/rp/spi_master.cpp.in
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "spi_master_{{id}}.hpp"
+#include
+
+// ----------------------------------------------------------------------------
+
+
+void modm::platform::SpiMaster{{ id }}::reset()
+{
+ Resets::reset(RESETS_RESET_SPI{{ id }}_BITS);
+}
+
+void modm::platform::SpiMaster{{ id }}::unreset()
+{
+ Resets::unresetWait(RESETS_RESET_SPI{{ id }}_BITS);
+}
+
+uint8_t
+modm::platform::SpiMaster{{ id }}::acquire(void *ctx, ConfigurationHandler handler)
+{
+ if (context == nullptr)
+ {
+ context = ctx;
+ count = 1;
+ // if handler is not nullptr and is different from previous configuration
+ if (handler and configuration != handler) {
+ configuration = handler;
+ configuration();
+ }
+ return 1;
+ }
+
+ if (ctx == context)
+ return ++count;
+
+ return 0;
+}
+
+uint8_t
+modm::platform::SpiMaster{{ id }}::release(void *ctx)
+{
+ if (ctx == context)
+ {
+ if (--count == 0)
+ context = nullptr;
+ }
+ return count;
+}
+// ----------------------------------------------------------------------------
+
+modm::ResumableResult
+modm::platform::SpiMaster{{ id }}::transfer(uint8_t data)
+{
+ // this is a manually implemented "fast resumable function"
+ // there is no context or nesting protection, since we don't need it.
+ // there are only two states encoded into 1 bit (LSB of state):
+ // 1. waiting to start, and
+ // 2. waiting to finish.
+
+ // LSB != Bit0 ?
+ if ( !(state & Bit0) )
+ {
+ // wait for previous transfer to finish
+ if (txFifoFull())
+ return {modm::rf::Running};
+
+ // start transfer by copying data into register
+ write(data);
+
+ // set LSB = Bit0
+ state |= Bit0;
+ }
+
+ if (rxFifoEmpty())
+ return {modm::rf::Running};
+
+ // transfer finished
+ state &= ~Bit0;
+ return {modm::rf::Stop, read()};
+}
+
+modm::ResumableResult
+modm::platform::SpiMaster{{ id }}::transfer(
+ const uint8_t * tx, uint8_t * rx, std::size_t length)
+{
+ // this is a manually implemented "fast resumable function"
+ // there is no context or nesting protection, since we don't need it.
+ // there are only two states encoded into 1 bit (Bit1 of state):
+ // 1. initialize index, and
+ // 2. wait for 1-byte transfer to finish.
+
+ // we need to globally remember which byte we are currently transferring
+ static std::size_t index = 0;
+
+ // we are only interested in Bit1
+ switch(state & Bit1)
+ {
+ case 0:
+ // we will only visit this state once
+ state |= Bit1;
+
+ // initialize index and check range
+ index = 0;
+ while (index < length)
+ {
+ default:
+ {
+ // if tx == 0, we use a dummy byte 0x00
+ // else we copy it from the array
+ // call the resumable function
+ modm::ResumableResult result = transfer(tx ? tx[index] : 0);
+
+ // if the resumable function is still running, so are we
+ if (result.getState() > modm::rf::NestingError)
+ return {modm::rf::Running};
+
+ // if rx != 0, we copy the result into the array
+ if (rx) rx[index] = result.getResult();
+ }
+ index++;
+ }
+
+ // clear the state
+ state &= ~Bit1;
+ return {modm::rf::Stop};
+ }
+}
+
+void modm::platform::SpiMaster{{ id }}::transferBlocking(
+ const uint8_t *tx, std::size_t length)
+{
+ uint8_t index = 0;
+ while(index < length) {
+ // Wait for tx empty
+ while(txFifoFull()) __NOP();
+ // Write next byte
+ write(tx ? tx[index] : 0);
+ index++;
+ }
+
+ drainRx();
+}
+
diff --git a/src/modm/platform/spi/rp/spi_master.hpp.in b/src/modm/platform/spi/rp/spi_master.hpp.in
new file mode 100644
index 0000000000..614e977784
--- /dev/null
+++ b/src/modm/platform/spi/rp/spi_master.hpp.in
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+{
+
+/**
+ * Serial peripheral interface SPI{{ id }}.
+ *
+ * @author Andrey Kunitsyn
+ * @ingroup modm_platform_spi modm_platform_spi_{{id}}
+ */
+class SpiMaster{{ id }} : public modm::SpiMaster
+{
+ // Bit0: single transfer state
+ // Bit1: block transfer state
+ static inline uint8_t state{0};
+ static inline uint8_t count{0};
+ static inline void *context{nullptr};
+ static inline ConfigurationHandler configuration{nullptr};
+
+protected:
+ static inline bool isBusy() {
+ return (spi{{ id }}_hw->sr & SPI_SSPSR_BSY_BITS);
+ }
+
+ static inline bool txFifoFull() {
+ return (spi{{ id }}_hw->sr & SPI_SSPSR_TNF_BITS) == 0;
+ }
+ static inline bool txFifoEmpty() {
+ return spi{{ id }}_hw->sr & SPI_SSPSR_TFE_BITS;
+ }
+
+ static inline bool rxFifoEmpty() {
+ return (spi{{ id }}_hw->sr & SPI_SSPSR_RNE_BITS) == 0;
+ }
+
+ static inline void drainRx()
+ {
+ // Drain RX FIFO, then wait for shifting to finish (which may be *after*
+ // TX FIFO drains), then drain RX FIFO again
+ while (!rxFifoEmpty())
+ (void)read();
+ while (isBusy())
+ __NOP();
+ while (!rxFifoEmpty())
+ (void)read();;
+
+ // Don't leave overrun flag set
+ spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS;
+ }
+ /*
+ * Find smallest prescale value which puts output frequency in range of
+ * post-divide. Prescale is an even number from 2 to 254 inclusive.
+ */
+ template
+ constexpr static uint32_t
+ calcPrescale()
+ {
+ uint32_t prescale;
+ for (prescale = 2; prescale <= 254; prescale += 2) {
+ if (freq_in < (prescale + 2) * 256 * (uint64_t) baudrate)
+ break;
+ }
+ return prescale;
+ }
+public:
+ template< class... Signals >
+ static void
+ connect()
+ {
+ using Connector = GpioConnector;
+ using Sclk = typename Connector::template GetSignal< Gpio::Signal::Sclk >;
+ using Mosi = typename Connector::template GetSignal< Gpio::Signal::Tx >;
+ /*using Miso = typename Connector::template GetSignal< Gpio::Signal::Rx >;*/
+ static_assert(Connector::template IsValid);
+ static_assert(Connector::template IsValid);
+
+ Connector::connect();
+ }
+
+ static void reset();
+ static void unreset();
+
+ static void
+ setFormat(uint8_t data_bits, DataMode mode, DataOrder order)
+ {
+ modm_assert(data_bits >= 4 && data_bits <= 16, "SPI data_bits", "SPI data bits out of range");
+ // LSB-first not supported on PL022:
+ modm_assert(order == DataOrder::MsbFirst, "SPI DataOrder", "SPI hardware does not support alternate transmit order");
+ hw_write_masked(&spi{{ id }}_hw->cr0,
+ ((uint32_t(data_bits - 1)) << SPI_SSPCR0_DSS_LSB) |
+ (((uint32_t(mode)&2)>>1) << SPI_SSPCR0_SPO_LSB) |
+ ((uint32_t(mode)&1) << SPI_SSPCR0_SPH_LSB),
+ SPI_SSPCR0_DSS_BITS |
+ SPI_SSPCR0_SPO_BITS |
+ SPI_SSPCR0_SPH_BITS);
+ }
+
+ template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5)>
+ static uint32_t
+ setBaudrate()
+ {
+ constexpr uint32_t freq_in = SystemClock::PeriFrequency;
+ constexpr uint32_t prescale = calcPrescale();
+ static_assert(prescale <= 254, "Frequency too low");
+ static_assert(freq_in / prescale <= 256 * (uint64_t) baudrate, "Failed calc prescale");
+
+ // Find largest post-divide which makes output closest to baudrate. Post-divide is
+ // an integer in the range 1 to 256 inclusive.
+ constexpr uint32_t postdiv = Prescaler::from_range(freq_in / prescale, baudrate, 1, 256).prescaler;
+
+ constexpr uint32_t result_baudrate = freq_in / (prescale * postdiv);
+
+ assertBaudrateInTolerance< result_baudrate, baudrate, tolerance >();
+
+ spi{{ id }}_hw->cpsr = prescale;
+ hw_write_masked(&spi{{ id }}_hw->cr0, (postdiv - 1) << SPI_SSPCR0_SCR_LSB, SPI_SSPCR0_SCR_BITS);
+
+ return result_baudrate;
+ }
+
+ // start documentation inherited
+ template< class SystemClock, baudrate_t baudrate, percent_t tolerance=5_pct >
+ static void
+ initialize()
+ {
+ reset();
+ unreset();
+
+ setBaudrate();
+ setFormat(8,DataMode::Mode0, DataOrder::MsbFirst);
+ // Set master mode
+ hw_clear_bits(&spi{{ id }}_hw->cr1, SPI_SSPCR1_MS_BITS);
+ // Finally enable the SPI
+ hw_set_bits(&spi{{ id }}_hw->cr1, SPI_SSPCR1_SSE_BITS);
+ }
+
+ static void
+ setDataMode(DataMode mode)
+ {
+ hw_write_masked(&spi{{ id }}_hw->cr0,
+ (((uint32_t(mode)&2)>>1) << SPI_SSPCR0_SPO_LSB) |
+ ((uint32_t(mode)&1) << SPI_SSPCR0_SPH_LSB),
+ SPI_SSPCR0_SPO_BITS |
+ SPI_SSPCR0_SPH_BITS);
+ }
+
+ static void
+ setDataOrder(DataOrder order)
+ {
+ modm_assert(order == DataOrder::MsbFirst, "SPI DataOrder", "SPI hardware does not support alternate transmit order");
+ }
+
+ static uint8_t
+ acquire(void *ctx, ConfigurationHandler handler = nullptr);
+
+ static uint8_t
+ release(void *ctx);
+
+ static uint8_t
+ transferBlocking(uint8_t data)
+ {
+ return RF_CALL_BLOCKING(transfer(data));
+ }
+
+ static void
+ transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length)
+ {
+ // If we do not need to receive data, use a more efficient
+ // transmit-only routine to increase throughput
+ if(rx) {
+ RF_CALL_BLOCKING(transfer(tx, rx, length));
+ } else {
+ transferBlocking(tx, length);
+ }
+ }
+
+ static modm::ResumableResult
+ transfer(uint8_t data);
+
+ static modm::ResumableResult
+ transfer(const uint8_t *tx, uint8_t *rx, std::size_t length);
+ // end documentation inherited
+
+protected:
+ /** Perform transmit-only transaction
+ *
+ * A faster version of blocking transfer when transmitting only.
+ *
+ * If no receive is needed, the next byte can be loaded while the
+ * current transfer is in progress.
+ */
+ static void
+ transferBlocking(const uint8_t *tx, std::size_t length);
+
+ static inline void
+ write(uint8_t data)
+ {
+ spi{{ id }}_hw->dr = uint32_t(data);
+ }
+
+ static inline uint8_t
+ read()
+ {
+ return uint8_t(spi{{ id }}_hw->dr);
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/platform/spi/rp/spi_master_dma.hpp.in b/src/modm/platform/spi/rp/spi_master_dma.hpp.in
new file mode 100644
index 0000000000..0b59e3f2f3
--- /dev/null
+++ b/src/modm/platform/spi/rp/spi_master_dma.hpp.in
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "spi_master_{{ id }}.hpp"
+
+namespace modm::platform
+{
+
+/**
+ * Serial peripheral interface (SPI{{ id }}) with DMA support.
+ *
+ * This class uses the DMA controller for sending and receiving data, which
+ * greatly reduces the CPU load. Beside passing the DMA channels as template
+ * parameters the class can be used in the same way like the SpiMaster{{ id }}.
+ *
+ * @tparam DmaChannelRX DMA channel for receiving
+ * @tparam DmaChannelTX DMA channel for sending
+ *
+ * @author Andrey Kunitsyn
+ * @ingroup modm_platform_spi modm_platform_spi_{{id}}
+ */
+template
+class SpiMaster{{ id }}_Dma : public SpiMaster{{ id }}
+{
+ struct Dma
+ {
+ using RxChannel = DmaChannelRx;
+ using TxChannel = DmaChannelTx;
+ static constexpr auto RxRequest = DmaBase::Request::Spi{{id}}_Rx;
+ static constexpr auto TxRequest = DmaBase::Request::Spi{{id}}_Tx;
+ static constexpr uint32_t DreqTx = SPI_SSPDMACR_TXDMAE_BITS;
+ static constexpr uint32_t DreqRx = SPI_SSPDMACR_RXDMAE_BITS;
+ static_assert(TxChannel::mask, "Tx Channel cannot be a dummy channel!");
+ };
+protected:
+ static inline void disableDreq(uint32_t mask) {
+ hw_clear_bits(&spi{{ id }}_hw->dmacr,mask);
+ }
+ static inline void enableDreq(uint32_t mask) {
+ hw_set_bits(&spi{{ id }}_hw->dmacr,mask);
+ }
+public:
+ /// Initialize DMA and SPI{{ id }}
+ template
+ static void
+ initialize();
+
+ static uint8_t
+ transferBlocking(uint8_t data)
+ {
+ return RF_CALL_BLOCKING(transfer(data));
+ }
+
+ static void
+ transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length)
+ {
+ RF_CALL_BLOCKING(transfer(tx, rx, length));
+ }
+
+ static modm::ResumableResult
+ transfer(uint8_t data);
+
+ static modm::ResumableResult
+ transfer(const uint8_t *tx, uint8_t *rx, std::size_t length);
+
+ static void
+ startTransfer(const uint8_t *tx, uint8_t *rx, std::size_t length);
+
+ template
+ static void waitCompleted(Wait wait);
+ static void waitCompleted()
+ {
+ waitCompleted([](){});
+ }
+
+private:
+ // needed for transfers where no RX or TX buffers are given
+ static inline uint8_t dmaDummy{0};
+};
+
+} // namespace modm::platform
+#include "spi_master_{{ id }}_dma_impl.hpp"
diff --git a/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in
new file mode 100644
index 0000000000..8cfd58222c
--- /dev/null
+++ b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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 "spi_master_{{ id }}_dma.hpp"
+
+template
+template
+void
+modm::platform::SpiMaster{{ id }}_Dma::initialize()
+{
+ // Configure the DMA channels, then calls SpiMaster{{ id }}::initialzie().
+ Dma::RxChannel::configure(DmaBase::TransferDataSize::Byte, false, true);
+ Dma::RxChannel::setReadAddress(uint32_t(&spi{{id}}_hw->dr));
+ Dma::RxChannel::template setPeripheralRequest();
+
+ Dma::TxChannel::configure(DmaBase::TransferDataSize::Byte, true, false);
+ Dma::TxChannel::setWriteAddress(uint32_t(&spi{{id}}_hw->dr));
+ Dma::TxChannel::template setPeripheralRequest();
+
+ SpiMaster{{ id }}::initialize();
+}
+
+template
+modm::ResumableResult
+modm::platform::SpiMaster{{ id }}_Dma::transfer(uint8_t data)
+{
+ // this is a manually implemented "fast resumable function"
+ // there is no context or nesting protection, since we don't need it.
+ // there are only two states encoded into 1 bit (LSB of state):
+ // 1. waiting to start, and
+ // 2. waiting to finish.
+ // LSB != Bit0 ?
+ if ( !(state & Bit0) )
+ {
+ // disable DMA for single byte transfer
+ disableDreq(Dma::DreqTx|Dma::DreqRx);
+ }
+ return SpiMaster{{ id }}::transfer(data);
+}
+
+template
+void
+modm::platform::SpiMaster{{ id }}_Dma::startTransfer(const uint8_t *tx,
+ uint8_t *rx, std::size_t length)
+{
+ if ( (!rx && !tx) or length == 0) {
+ return;
+ }
+ if constexpr (Dma::RxChannel::mask == 0) {
+ rx = nullptr;
+ enableDreq(Dma::DreqTx);
+ } else {
+ enableDreq((tx ? Dma::DreqTx : 0)|(rx ? Dma::DreqRx : 0));
+ }
+
+ if (tx) {
+ Dma::TxChannel::setReadAddress(uint32_t(tx));
+ Dma::TxChannel::setReadIncrementMode(true);
+ } else {
+ Dma::TxChannel::setReadAddress(uint32_t(&dmaDummy));
+ Dma::TxChannel::setReadIncrementMode(false);
+ }
+ Dma::TxChannel::setDataLength(length);
+ if constexpr (Dma::RxChannel::mask == 0) {
+ Dma::TxChannel::start();
+ } else {
+ if (rx) {
+ Dma::RxChannel::setWriteAddress(uint32_t(rx));
+ Dma::RxChannel::setWriteIncrementMode(true);
+ } else {
+ Dma::RxChannel::setWriteAddress(uint32_t(&dmaDummy));
+ Dma::RxChannel::setWriteIncrementMode(false);
+ }
+ Dma::RxChannel::setDataLength(length);
+ ::modm::platform::Dma::template start();
+ }
+}
+
+template
+template
+void
+modm::platform::SpiMaster{{ id }}_Dma::waitCompleted(Wait wait)
+{
+ while (Dma::TxChannel::isBusy() or Dma::RxChannel::isBusy())
+ wait();
+ while (!txFifoEmpty() or isBusy())
+ wait();
+ if constexpr (Dma::RxChannel::mask == 0) {
+ // Drain RX FIFO, then wait for shifting to finish (which may be *after*
+ // TX FIFO drains), then drain RX FIFO again
+ while (!rxFifoEmpty())
+ (void)read();
+ while (isBusy())
+ wait();
+ while (!rxFifoEmpty())
+ (void)read();
+
+ // Don't leave overrun flag set
+ spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS;
+ } else {
+ while (!rxFifoEmpty() or isBusy())
+ wait();
+ }
+}
+
+template
+modm::ResumableResult
+modm::platform::SpiMaster{{ id }}_Dma::transfer(const uint8_t *tx,
+ uint8_t *rx, std::size_t length)
+{
+ if ( (!rx && !tx) || length == 0) {
+ return {modm::rf::Stop};
+ }
+ if constexpr (Dma::RxChannel::mask == 0) rx = nullptr;
+ // this is a manually implemented "fast resumable function"
+ // there is no context or nesting protection, since we don't need it.
+ // there are only two states encoded into 1 bit (Bit1 of state):
+ // 1. initialize index, and
+ // 2. wait for 1-byte transfer to finish.
+
+ // we are only interested in Bit1
+ switch(state & Bit1)
+ {
+ case 0:
+ // we will only visit this state once
+ state |= Bit1;
+ startTransfer(tx,rx,length);
+
+ [[fallthrough]];
+
+ default:
+ while (true) {
+ if (Dma::TxChannel::isBusy() or (rx && Dma::RxChannel::isBusy()))
+ return { modm::rf::Running };
+ if (!txFifoEmpty() or (rx && !rxFifoEmpty()) or isBusy())
+ return { modm::rf::Running };
+ break;
+ }
+ if (!rx) {
+ // Drain RX FIFO, then wait for shifting to finish (which may be *after*
+ // TX FIFO drains), then drain RX FIFO again
+ while (!rxFifoEmpty())
+ (void)read();
+ while (isBusy())
+ return { modm::rf::Running };
+
+ // Don't leave overrun flag set
+ spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS;
+ }
+
+
+ disableDreq(Dma::DreqTx|Dma::DreqRx);
+ // clear the state
+ state &= ~Bit1;
+ return {modm::rf::Stop};
+ }
+}
diff --git a/src/modm/platform/uart/rp/module.lb b/src/modm/platform/uart/rp/module.lb
new file mode 100644
index 0000000000..720943752f
--- /dev/null
+++ b/src/modm/platform/uart/rp/module.lb
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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/.
+# -----------------------------------------------------------------------------
+
+
+class Instance(Module):
+ def __init__(self, instance):
+ self.instance = instance
+
+ def init(self, module):
+ module.name = str(self.instance)
+ module.description = "UART {} instance".format(self.instance)
+
+ def prepare(self, module, options):
+ module.add_option(
+ NumericOption(
+ name="buffer.tx",
+ description="",
+ minimum=32, maximum=2 ** 16 - 2,
+ default=32))
+ module.add_option(
+ NumericOption(
+ name="buffer.rx",
+ description="",
+ minimum=32, maximum=2 ** 16 - 2,
+ default=32))
+
+ return True
+
+ def build(self, env):
+ properties = {
+ "id": self.instance,
+ "fifo_size": 32,
+ }
+ env.substitutions = properties
+ env.outbasepath = "modm/src/modm/platform/uart"
+
+ env.template("uart.hpp.in", "uart_{}.hpp".format(self.instance))
+ env.template("uart.cpp.in", "uart_{}.cpp".format(self.instance))
+
+
+def init(module):
+ module.name = ":platform:uart"
+ module.description = "Universal Asynchronous Receiver Transmitter (UART)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("uart:rp*"):
+ return False
+
+ module.depends(
+ ":math:algorithm",
+ ":platform:gpio",
+ ":platform:clockgen",
+ ":architecture:uart",
+ ":architecture:interrupt")
+
+ for instance in listify(device.get_driver("uart")["instance"]):
+ module.add_submodule(Instance(instance))
+
+ return True
+
+def build(env):
+ pass
diff --git a/src/modm/platform/uart/rp/uart.cpp.in b/src/modm/platform/uart/rp/uart.cpp.in
new file mode 100644
index 0000000000..180978fec2
--- /dev/null
+++ b/src/modm/platform/uart/rp/uart.cpp.in
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2012, 2016-2017, Sascha Schade
+ * Copyright (c) 2013-2014, Kevin Läufer
+ * Copyright (c) 2013-2017, Niklas Hauser
+ * Copyright (c) 2017, Fabian Greif
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+#include
+
+#include "../device.hpp"
+
+#include "uart_{{ id }}.hpp"
+
+// ----------------------------------------------------------------------------
+
+namespace
+{
+// If requested buffer size is larger than hardware buffer size create a
+// software queue for the remaining bytes.
+%% if options["buffer.rx"] > fifo_size
+ static modm::atomic::Queue rxBuffer;
+%% endif
+%% if options["buffer.tx"] > fifo_size
+ static modm::atomic::Queue txBuffer;
+%% endif
+
+}
+
+static inline bool tx_fifo_full()
+{
+ return uart{{ id }}_hw->fr & UART_UARTFR_TXFF_BITS;
+}
+
+static inline bool rx_fifo_empty()
+{
+ return uart{{ id }}_hw->fr & UART_UARTFR_RXFE_BITS;
+}
+
+void modm::platform::Uart{{ id }}::reset()
+{
+ Resets::reset(RESETS_RESET_UART{{ id }}_BITS);
+}
+
+void modm::platform::Uart{{ id }}::unreset()
+{
+ Resets::unresetWait(RESETS_RESET_UART{{ id }}_BITS);
+}
+
+void
+modm::platform::Uart{{ id }}::writeBlocking(uint8_t data)
+{
+ while (not write(data));
+}
+
+void
+modm::platform::Uart{{ id }}::writeBlocking(const uint8_t *data, std::size_t length)
+{
+ while (length--) writeBlocking(*data++);
+}
+
+void
+modm::platform::Uart{{ id }}::flushWriteBuffer()
+{
+ while(!isWriteFinished());
+}
+
+// ----------------------------------------------------------------------------
+bool
+modm::platform::Uart{{ id }}::write(uint8_t data)
+{
+%% if options["buffer.tx"] <= fifo_size
+ // No software buffer necessary, use hardware buffer only.
+ if (tx_fifo_full()) return false;
+ uart{{ id }}_hw->dr = data;
+ return true;
+%% else
+ atomic::Lock lock;
+ // Use hardware buffer and additional software buffer.
+ uart{{ id }}_hw->imsc |= UART_UARTIMSC_TXIM_BITS;
+
+ if (txBuffer.isEmpty() and !tx_fifo_full())
+ {
+ // If software buffer is empty try to write to hardware buffer directly.
+ // Do not write to hardware buffer while there is some data in the
+ // software buffer to maintain byte order.
+ // There is at least charsLeft bytes free in the FIFO
+ uart{{ id }}_hw->dr = data;
+ return true; // success
+ }
+
+ // Hardware buffer is full, so try software buffer.
+ // Software buffer is empty so this will succeed.
+ // Hardware buffer is not empty so at least one Tx interrupt
+ // will be generated soon.
+
+ return txBuffer.push(data);
+%% endif
+}
+
+// ----------------------------------------------------------------------------
+std::size_t
+modm::platform::Uart{{ id }}::write(const uint8_t *buffer, std::size_t length)
+{
+ std::size_t written(0);
+
+%% if options["buffer.tx"] <= fifo_size
+ // No software buffer necessary, use hardware buffer only.
+ while ( !tx_fifo_full() and writtendr = *buffer++;
+ written++;
+ }
+%% else
+ atomic::Lock lock;
+ // Use hardware buffer and additional software buffer.
+ uart{{ id }}_hw->imsc |= UART_UARTIMSC_TXIM_BITS;
+
+ if (txBuffer.isEmpty())
+ {
+ // If software buffer is completly empty try to write to hardware buffer directly.
+ // Do not write to hardware buffer while there is some data in the
+ // software buffer to maintain byte order.
+
+ // First Copy max(length) chars from buffer to hardware FIFO.
+ while (written < length and !tx_fifo_full())
+ {
+ uart{{ id }}_hw->dr = *buffer++;
+ written++;
+ }
+ }
+
+ // If there is remaining data, put it into the software buffer
+ while (written < length)
+ {
+ if (not txBuffer.push(*buffer++)) break;
+ written++;
+ }
+%% endif
+ return written;
+}
+
+bool
+modm::platform::Uart{{ id }}::isWriteFinished()
+{
+%% if options["buffer.tx"] > fifo_size
+ return txBuffer.isEmpty() and ((uart{{ id }}_hw->fr & UART_UARTFR_BUSY_BITS) == 0);
+%% else
+ return (uart{{ id }}_hw->fr & UART_UARTFR_BUSY_BITS) == 0;
+%% endif
+}
+
+std::size_t
+modm::platform::Uart{{ id }}::discardTransmitBuffer()
+{
+%% if options["buffer.tx"] > fifo_size
+ atomic::Lock lock;
+ std::size_t count(0);
+ // disable interrupt since buffer will be cleared
+ uart{{ id }}_hw->imsc &= UART_UARTIMSC_TXIM_BITS;
+ while(not txBuffer.isEmpty())
+ {
+ ++count;
+ txBuffer.pop();
+ }
+ return count;
+%% else
+ return 0;
+%% endif
+}
+
+bool
+modm::platform::Uart{{ id }}::read(uint8_t& data)
+{
+%% if options["buffer.rx"] > fifo_size
+ if (not rxBuffer.isEmpty())
+ {
+ data = rxBuffer.get();
+ rxBuffer.pop();
+ // if the hardware buffer has been used, copy all into rxBuffer
+ while(!rx_fifo_empty() and rxBuffer.isNotFull())
+ {
+ rxBuffer.push(uart{{ id }}_hw->dr);
+ }
+ if (rxBuffer.isNotFull())
+ {
+ atomic::Lock lock;
+ uart{{ id }}_hw->imsc |= UART_UARTIMSC_RXIM_BITS;
+ }
+ return true;
+ }
+%% else
+ if ((uart{{ id }}_hw->fr & UART_UARTFR_RXFE_BITS) == 0)
+ {
+ // Receive data available
+ data = uart{{ id }}_hw->dr;
+ return true;
+ }
+%% endif
+ return false;
+}
+
+std::size_t
+modm::platform::Uart{{ id }}::read(uint8_t *buffer, std::size_t length)
+{
+ std::size_t ret = 0;
+
+ while (ret < length)
+ {
+ if (not read(*buffer++)) break;
+ ret++;
+ }
+
+ return ret;
+}
+
+std::size_t
+modm::platform::Uart{{ id }}::discardReceiveBuffer()
+{
+ std::size_t count(0);
+%% if options["buffer.rx"] > fifo_size
+ while(!rxBuffer.isEmpty())
+ {
+ ++count;
+ rxBuffer.pop();
+ }
+%% endif
+ while(!rx_fifo_empty())
+ {
+ (void)uart{{ id }}_hw->dr;
+ ++count;
+ }
+ return count;
+}
+
+// ----------------------------------------------------------------------------
+MODM_ISR(UART{{ id }}_IRQ)
+{
+ // read Masked Interrupt Status Register, UARTMIS
+ uint32_t IValue = uart{{ id }}_hw->mis;
+ // clear
+ uart{{ id }}_hw->icr = IValue;
+%% if options["buffer.tx"] > fifo_size
+ if (IValue & UART_UARTMIS_TXMIS_BITS)
+ {
+ while (!txBuffer.isEmpty() and !tx_fifo_full())
+ {
+ // Write to the hardware buffer
+ uart{{ id }}_hw->dr = txBuffer.get();
+ txBuffer.pop();
+ }
+ }
+%% endif
+%% if options["buffer.rx"] > fifo_size
+ if (IValue & UART_UARTMIS_RXMIS_BITS)
+ {
+ while(!rx_fifo_empty()) {
+ rxBuffer.push(uart{{ id }}_hw->dr);
+ }
+ if (rxBuffer.isFull()) {
+ uart{{ id }}_hw->imsc &= ~UART_UARTIMSC_RXIM_BITS;
+ }
+ }
+%% endif
+}
diff --git a/src/modm/platform/uart/rp/uart.hpp.in b/src/modm/platform/uart/rp/uart.hpp.in
new file mode 100644
index 0000000000..f45085b7ef
--- /dev/null
+++ b/src/modm/platform/uart/rp/uart.hpp.in
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+namespace modm::platform
+{
+
+
+/**
+ * Universal asynchronous receiver transmitter (UART)
+ *
+ * This implementation uses the hardware buffer and the software buffer.
+ * A software buffer is only used if more than {{ fifo_size }} bytes of buffering
+ * is requested.
+ *
+ *
+ * There is no FIFO-not-full flag. It's only possible to check if the
+ * FIFO is completely empty. This makes it impossible to add data
+ * to the FIFO after the first byte is put into the FIFO.
+ *
+ * After detecting that the FIFO is empty (THRE interrupt) the
+ * charsLeft variable is set to {{ fifo_size }} (= size of FIFO) and some accounting
+ * is done in this class.
+ *
+ * @ingroup modm_platform_uart
+ * @author Andrey Kunitsyn
+ */
+class Uart{{ id }} : public modm::Uart
+{
+ static void
+ initialize(uint32_t baudrate);
+
+public:
+ // Expose jinja template parameters to be checked by e.g. drivers or application
+ static constexpr size_t RxBufferSize = {{ options["buffer.rx"] }};
+ static constexpr size_t TxBufferSize = {{ options["buffer.tx"] }};
+
+public:
+ enum class
+ Parity : uint32_t
+ {
+ Even = UART_UARTLCR_H_PEN_BITS | UART_UARTLCR_H_EPS_BITS,
+ Odd = UART_UARTLCR_H_PEN_BITS,
+ Disabled = 0,
+ };
+
+ enum class
+ WordLength : uint32_t
+ {
+ Bit5 = 0 << UART_UARTLCR_H_WLEN_LSB,
+ Bit6 = 1 << UART_UARTLCR_H_WLEN_LSB,
+ Bit7 = 2 << UART_UARTLCR_H_WLEN_LSB,
+ Bit8 = 3 << UART_UARTLCR_H_WLEN_LSB,
+ };
+
+ enum class
+ StopBits : uint32_t
+ {
+ Bit1 = 0,
+ Bit2 = UART_UARTLCR_H_STP2_BITS,
+ };
+
+ template< class... Signals >
+ static void
+ connect()
+ {
+ using Connector = GpioConnector;
+ using Tx = typename Connector::template GetSignal< Gpio::Signal::Tx >;
+ using Rx = typename Connector::template GetSignal< Gpio::Signal::Rx >;
+ static_assert(((Connector::template IsValid and Connector::template IsValid) and sizeof...(Signals) == 2) or
+ ((Connector::template IsValid or Connector::template IsValid) and sizeof...(Signals) == 1),
+ "Uart{{ id }}::connect() requires one Tx and/or one Rx signal!");
+
+ Connector::connect();
+ }
+
+ static void reset();
+ static void unreset();
+
+ static void
+ setFormat(Parity parity, WordLength length, StopBits stop)
+ {
+ hw_write_masked(&uart{{ id }}_hw->lcr_h,
+ uint32_t(parity) | uint32_t(length) | uint32_t(stop),
+ UART_UARTLCR_H_WLEN_BITS | UART_UARTLCR_H_STP2_BITS |
+ UART_UARTLCR_H_PEN_BITS | UART_UARTLCR_H_EPS_BITS);
+ }
+
+ template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(1) >
+ static baudrate_t
+ setBaudrate()
+ {
+ static_assert(baudrate * 16 <= SystemClock::PeriFrequency and
+ SystemClock::PeriFrequency <= baudrate * 16 * 65535ull,
+ "SystemClock::PeriFrequency must be in the range [16 x baudrate, 16 x 65535 x baudrate].");
+ // 16.6 fractional baudrate generator with 16x oversampling
+ constexpr uint32_t min = (1ul << 7);
+ constexpr uint32_t max = (1ul << 22) - 1ul;
+ constexpr auto result = Prescaler::from_range(SystemClock::PeriFrequency*4, baudrate, min, max);
+ modm::PeripheralDriver::assertBaudrateInTolerance< result.frequency, baudrate, tolerance >();
+ // Load PL011's baud divisor registers
+ uart{{ id }}_hw->ibrd = result.prescaler >> 6;
+ uart{{ id }}_hw->fbrd = result.prescaler & 0x3f;
+ // PL011 needs a (dummy) line control register write to latch in the
+ // divisors. We don't want to actually change LCR contents here.
+ hw_set_bits(&uart{{ id }}_hw->lcr_h, 0);
+
+ return result.frequency;
+ }
+
+ template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(1) >
+ static void
+ initialize(Parity parity=Parity::Disabled, WordLength length=WordLength::Bit8, StopBits stop=StopBits::Bit1)
+ {
+ reset();
+ unreset();
+ setBaudrate();
+ setFormat(parity, length, stop);
+ // Enable the UART, both TX and RX
+ uart{{ id }}_hw->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS;
+ // Enable FIFOs
+ hw_set_bits(&uart{{ id }}_hw->lcr_h, UART_UARTLCR_H_FEN_BITS);
+ // Always enable DREQ signals -- no harm in this if DMA is not listening
+ uart{{ id }}_hw->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS;
+
+%% if options["buffer.rx"] > fifo_size
+ /* If RX buffering with more than {{ fifo_size }} bytes is requested a software queue
+ * must be used for receiving. This involves the Rx Interrupt only. */
+ uart{{ id }}_hw->imsc = UART_UARTIMSC_RXIM_BITS;
+%% endif
+
+%% if options["buffer.tx"] > fifo_size or options["buffer.rx"] > fifo_size
+ /* Enable the UART Interrupt */
+ NVIC_EnableIRQ(UART{{ id }}_IRQ_IRQn);
+%% endif
+ }
+
+ static void
+ writeBlocking(uint8_t data);
+
+ static void
+ writeBlocking(const uint8_t *data, std::size_t length);
+
+ static void
+ flushWriteBuffer();
+
+ static bool
+ write(uint8_t data);
+
+ static std::size_t
+ write(const uint8_t *data, std::size_t length);
+
+ static bool
+ isWriteFinished();
+
+ static std::size_t
+ discardTransmitBuffer();
+
+ static bool
+ read(uint8_t &data);
+
+ static std::size_t
+ read(uint8_t *buffer, std::size_t length);
+
+ static std::size_t
+ discardReceiveBuffer();
+};
+
+} // namespace modm::platform
+
diff --git a/src/modm/platform/usb/rp/module.lb b/src/modm/platform/usb/rp/module.lb
new file mode 100644
index 0000000000..34681d27d3
--- /dev/null
+++ b/src/modm/platform/usb/rp/module.lb
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, Andrey Kunitsyn
+#
+# 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:usb"
+ module.description = "Universal Serial Bus (USB)"
+
+def prepare(module, options):
+ device = options[":target"]
+ if not device.has_driver("usb:rp20*"):
+ return False
+
+
+ module.depends(
+ ":architecture:interrupt",
+ ":cmsis:device",
+ ":platform:gpio")
+
+ return True
+
+def build(env):
+ env.outbasepath = "modm/src/modm/platform/usb"
+ env.copy('usb.hpp')
+ if env.has_module(":tinyusb:device:cdc"):
+ env.copy(repopath("ext/hathach/uart.hpp"), "uart.hpp")
diff --git a/src/modm/platform/usb/rp/usb.hpp b/src/modm/platform/usb/rp/usb.hpp
new file mode 100644
index 0000000000..5db0ea8728
--- /dev/null
+++ b/src/modm/platform/usb/rp/usb.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022, Andrey Kunitsyn
+ *
+ * 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
+
+namespace modm::platform
+{
+
+/// @ingroup modm_platform_usb
+class Usb
+{
+public:
+ template
+ static void
+ initialize(uint8_t priority = 3)
+ {
+ static_assert(SystemClock::UsbFrequency == 48_MHz, "USB must have a 48MHz clock!");
+ NVIC_SetPriority(USBCTRL_IRQ_IRQn, priority);
+ }
+
+ template
+ static void
+ connect()
+ {
+ using Connector = GpioConnector;
+ // Dp, Dm use dedicated pins, but Vbus and Overcurrent can be connected
+ Connector::connect();
+ }
+};
+
+} // namespace modm::platform
diff --git a/src/modm/processing/fiber/fiber.hpp b/src/modm/processing/fiber/fiber.hpp.in
similarity index 81%
rename from src/modm/processing/fiber/fiber.hpp
rename to src/modm/processing/fiber/fiber.hpp.in
index c1aeef8382..accbcb94a8 100644
--- a/src/modm/processing/fiber/fiber.hpp
+++ b/src/modm/processing/fiber/fiber.hpp.in
@@ -9,15 +9,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------
+%% if multicore
+%% set CoreDecl = ", size_t core=0"
+%% set CoreArg = ", size_t core"
+%% set CoreForward = ", core"
+%% else
+%% set CoreDecl = ""
+%% set CoreArg = ""
+%% set CoreForward = ""
+%% endif
#pragma once
#include "context.h"
#include "stack.hpp"
#include
-
namespace modm
{
-
namespace fiber
{
@@ -50,11 +57,11 @@ class Fiber
public:
template
- Fiber(fiber::Stack& stack, void(*fn)());
+ Fiber(fiber::Stack& stack, void(*fn)(){{CoreDecl}});
template
requires requires { &std::decay_t::operator(); }
- Fiber(fiber::Stack& stack, T&& closure);
+ Fiber(fiber::Stack