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