diff --git a/README.md b/README.md
index 1c5b16b4d7..59191c0e00 100644
--- a/README.md
+++ b/README.md
@@ -360,7 +360,7 @@ Please [discover modm's peripheral drivers for your specific device][discover].
✅ |
✅ |
○ |
-○ |
+✅ |
✅ |
✅ |
✅ |
diff --git a/examples/samg55_xplained_pro/spi-loopback/main.cpp b/examples/samg55_xplained_pro/spi-loopback/main.cpp
new file mode 100644
index 0000000000..e0005be295
--- /dev/null
+++ b/examples/samg55_xplained_pro/spi-loopback/main.cpp
@@ -0,0 +1,36 @@
+#include
+
+using namespace modm::platform;
+
+int main() {
+ // Test SPI send and receive in loopback mode. If we receive the expected
+ // characters back, flash slowly. Otherwise, flash fast.
+ Board::initialize();
+
+ SpiMaster0::connect();
+ SpiMaster0::initialize();
+
+ SpiMaster0::setLocalLoopback(true);
+
+ while (true)
+ {
+ uint32_t flash_time_ms;
+ uint8_t tx[] = {0xa5, 0x21};
+ uint8_t rx[2];
+
+ SpiMaster0::transferBlocking(tx, rx, 2);
+
+ if(rx[0] == 0xa5 && rx[1] == 0x21) {
+ flash_time_ms = 500;
+ } else {
+ flash_time_ms = 100;
+ }
+
+ for(uint32_t i=0; i<5; i++) {
+ Board::Led::set();
+ modm::delay_ms(flash_time_ms);
+ Board::Led::reset();
+ modm::delay_ms(flash_time_ms);
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/samg55_xplained_pro/spi-loopback/project.xml b/examples/samg55_xplained_pro/spi-loopback/project.xml
new file mode 100644
index 0000000000..43d375086e
--- /dev/null
+++ b/examples/samg55_xplained_pro/spi-loopback/project.xml
@@ -0,0 +1,11 @@
+
+ modm:samg55-xplained-pro
+
+
+
+
+
+ modm:build:scons
+ modm:platform:spi:0
+
+
\ No newline at end of file
diff --git a/src/modm/platform/clock/samg/clockgen.hpp b/src/modm/platform/clock/samg/clockgen.hpp
index c3b745ba5f..7e282a4b7f 100644
--- a/src/modm/platform/clock/samg/clockgen.hpp
+++ b/src/modm/platform/clock/samg/clockgen.hpp
@@ -44,14 +44,14 @@ enum class MainInternalFreq : uint32_t {
};
enum class ClockPeripheral : uint32_t {
- FlexCom0 = ID_FLEXCOM0,
- FlexCom1 = ID_FLEXCOM1,
- FlexCom2 = ID_FLEXCOM2,
- FlexCom3 = ID_FLEXCOM3,
- FlexCom4 = ID_FLEXCOM4,
- FlexCom5 = ID_FLEXCOM5,
- FlexCom6 = ID_FLEXCOM6,
- FlexCom7 = ID_FLEXCOM7,
+ Flexcom0 = ID_FLEXCOM0,
+ Flexcom1 = ID_FLEXCOM1,
+ Flexcom2 = ID_FLEXCOM2,
+ Flexcom3 = ID_FLEXCOM3,
+ Flexcom4 = ID_FLEXCOM4,
+ Flexcom5 = ID_FLEXCOM5,
+ Flexcom6 = ID_FLEXCOM6,
+ Flexcom7 = ID_FLEXCOM7,
PioA = ID_PIOA,
PioB = ID_PIOB,
Pdmic0 = ID_PDMIC0,
diff --git a/src/modm/platform/gpio/sam/pin.hpp.in b/src/modm/platform/gpio/sam/pin.hpp.in
index 73fad6d9f1..e988fa11da 100644
--- a/src/modm/platform/gpio/sam/pin.hpp.in
+++ b/src/modm/platform/gpio/sam/pin.hpp.in
@@ -37,6 +37,9 @@ enum class PeripheralPin
Dm,
Dp,
Wo,
+ Sck,
+ Miso,
+ Mosi
};
template
@@ -418,6 +421,9 @@ public:
using ExtInt = As;
using Dm = As;
using Dp = As;
+ using Sck = As;
+ using Miso = As;
+ using Mosi = As;
inline static bool
read()
diff --git a/src/modm/platform/spi/sam/module.lb b/src/modm/platform/spi/sam/module.lb
new file mode 100644
index 0000000000..c67f28a698
--- /dev/null
+++ b/src/modm/platform/spi/sam/module.lb
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021, Jeff McBride
+#
+# This file is part of the modm project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# -----------------------------------------------------------------------------
+
+def get_properties(env):
+ device = env[":target"]
+ driver = device.get_driver("spi")
+ properties = {}
+ properties["target"] = device.identifier
+ properties["features"] = driver["feature"] if "feature" in driver else []
+ return properties
+
+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):
+ module.depends(":platform:spi")
+ return True
+
+ def build(self, env):
+ properties = get_properties(env)
+ properties["id"] = self.instance
+
+ env.substitutions = properties
+ 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))
+
+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:samg*"):
+ return False
+
+ module.depends(
+ ":architecture:register",
+ ":architecture:spi",
+ ":cmsis:device",
+ ":math:algorithm",
+ ":platform:gpio",
+ ":platform:clockgen")
+
+ for driver in device.get_all_drivers("spi:samg*"):
+ for instance in driver["instance"]:
+ module.add_submodule(Instance(driver, instance))
+
+ return True
+
+def build(env):
+ env.substitutions = get_properties(env)
+ env.outbasepath = "modm/src/modm/platform/spi"
diff --git a/src/modm/platform/spi/sam/spi_master.cpp.in b/src/modm/platform/spi/sam/spi_master.cpp.in
new file mode 100644
index 0000000000..382f24cfe8
--- /dev/null
+++ b/src/modm/platform/spi/sam/spi_master.cpp.in
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009, Martin Rosekeit
+ * Copyright (c) 2009-2012, Fabian Greif
+ * Copyright (c) 2010, Georgi Grinshpun
+ * Copyright (c) 2012-2017, Niklas Hauser
+ * Copyright (c) 2013, Kevin Läufer
+ * Copyright (c) 2014, Sascha Schade
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * This file is part of the modm project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+// ----------------------------------------------------------------------------
+
+#include "spi_master_{{id}}.hpp"
+
+// ----------------------------------------------------------------------------
+
+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 (!isTransmitDataRegisterEmpty())
+ return {modm::rf::Running};
+
+ // start transfer by copying data into register
+ write(data);
+
+ // set LSB = Bit0
+ state |= Bit0;
+ }
+
+ if (!isReceiveDataRegisterFull())
+ 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};
+ }
+}
diff --git a/src/modm/platform/spi/sam/spi_master.hpp.in b/src/modm/platform/spi/sam/spi_master.hpp.in
new file mode 100644
index 0000000000..6c839c963d
--- /dev/null
+++ b/src/modm/platform/spi/sam/spi_master.hpp.in
@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2021, Jeff McBride
+ *
+ * This file is part of the modm project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+// ----------------------------------------------------------------------------
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace modm::platform {
+
+/**
+ * Serial peripheral interface on FLEXCOM{{ id }}.
+ *
+ * @author Jeff McBride
+ * @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};
+
+ // Work around a name collision between 'struct modm::Spi' and 'struct Spi'
+ // defined in the CMSIS headers
+ static inline ::Spi*
+ Regs() { return (::Spi*) SPI{{ id }}; };
+
+ static inline bool
+ isTransmitDataRegisterEmpty() {
+ return Regs()->SPI_SR & SPI_SR_TDRE;
+ }
+
+ static inline bool
+ isReceiveDataRegisterFull() {
+ return Regs()->SPI_SR & SPI_SR_RDRF;
+ }
+
+ static inline void
+ write(uint8_t data) {
+ Regs()->SPI_TDR = data;
+ }
+
+ static inline uint8_t
+ read() {
+ return (uint8_t)Regs()->SPI_RDR;
+ }
+
+public:
+ template< class... Pins >
+ static void
+ connect()
+ {
+ using SckPin = GetPin_t;
+ using MisoPin = GetPin_t;
+ using MosiPin = GetPin_t;
+ using Flexcom = Peripherals::Flexcom<{{ id | int }}>;
+
+ if constexpr (!std::is_void::value) {
+ using SckConnector = typename SckPin::template Connector;
+ SckPin::setOutput();
+ SckConnector::connect();
+ }
+ if constexpr (!std::is_void::value) {
+ using MisoConnector = typename MisoPin::template Connector;
+ MisoPin::setOutput();
+ MisoConnector::connect();
+ }
+ if constexpr (!std::is_void::value) {
+ using MosiConnector = typename MosiPin::template Connector;
+ MosiPin::setInput(modm::platform::InputType::Floating);
+ MosiConnector::connect();
+ }
+ }
+
+ // start documentation inherited
+ template< class SystemClock, baudrate_t baudrate, percent_t tolerance=5_pct >
+ static void
+ initialize() {
+ ClockGen::enable();
+
+ FLEXCOM{{ id }}->FLEXCOM_MR = FLEXCOM_MR_OPMODE_SPI;
+ // Note: SPI peripheral supports operating from a programmable clock
+ // output, but here we only use the main peripheral clock
+ constexpr auto result = modm::Prescaler::from_range(SystemClock::Frequency, baudrate, 2, 256);
+ assertBaudrateInTolerance< result.frequency, baudrate, tolerance >();
+
+ Regs()->SPI_CSR[0] = SPI_CSR_SCBR(result.prescaler);
+ Regs()->SPI_MR = SPI_MR_MSTR;
+ Regs()->SPI_CR |= SPI_CR_SPIEN;
+ }
+
+ static void
+ setDataMode(DataMode mode) {
+ uint32_t value = (uint32_t)mode ^ 2; // Flip CPHA bit
+ Regs()->SPI_CSR[0] = (Regs()->SPI_CSR[0] & ~(SPI_CSR_CPOL | SPI_CSR_NCPHA)) | (value << SPI_CSR_CPOL_Pos);
+ }
+
+ 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) {
+ 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);
+ // end documentation inherited
+
+ /** Enable local loopback mode, to internally connect MOSI to MISO */
+ static void
+ setLocalLoopback(bool enable) {
+ if(enable) {
+ Regs()->SPI_MR |= SPI_MR_LLB;
+ } else {
+ Regs()->SPI_MR &= ~SPI_MR_LLB;
+ }
+ }
+};
+
+} // namespace modm::platform