From cc50da690bf35f8d5309bff696421d412e75a715 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Thu, 29 Aug 2019 13:43:21 +0200 Subject: [PATCH] sys/arduino: Added SPI interface Added an Arduino compatible SPI API on top of RIOT's SPI API. --- sys/Makefile.dep | 1 + sys/arduino/SPI.cpp | 149 ++++++++++++++++++++ sys/arduino/include/SPI.h | 44 ++++++ sys/arduino/include/spiport.hpp | 236 ++++++++++++++++++++++++++++++++ 4 files changed, 430 insertions(+) create mode 100644 sys/arduino/SPI.cpp create mode 100644 sys/arduino/include/SPI.h create mode 100644 sys/arduino/include/spiport.hpp diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 4b40c3a632..40aaa3b671 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -1,5 +1,6 @@ ifneq (,$(filter arduino,$(USEMODULE))) FEATURES_OPTIONAL += periph_i2c + FEATURES_OPTIONAL += periph_spi endif ifneq (,$(filter eepreg,$(USEMODULE))) diff --git a/sys/arduino/SPI.cpp b/sys/arduino/SPI.cpp new file mode 100644 index 0000000000..c21378e474 --- /dev/null +++ b/sys/arduino/SPI.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup sys_arduino_api + * @{ + * + * @file + * @brief Implementation of the Arduino 'SPI' interface + * + * @author Marian Buschsieweke + * + * @} + */ + +#ifdef MODULE_PERIPH_SPI + +extern "C" { +#include "assert.h" +} + +#include "SPI.h" + +SPISettings::SPISettings(uint32_t clock_hz, uint8_t bitOrder, uint8_t dataMode) +{ + static const spi_clk_t clocks[] = { + SPI_CLK_10MHZ, SPI_CLK_5MHZ, SPI_CLK_1MHZ, SPI_CLK_400KHZ + }; + static const uint32_t steps [] = { + 1000000, 5000000, 1000000, 400000 + }; + + assert(bitOrder == MSBFIRST); + switch(dataMode) { + default: + case SPI_MODE0: + mode = SPI_MODE_0; + break; + case SPI_MODE1: + mode = SPI_MODE_1; + break; + case SPI_MODE2: + mode = SPI_MODE_2; + break; + case SPI_MODE3: + mode = SPI_MODE_3; + break; + } + + for (uint8_t i = 0; i < ARRAY_SIZE(steps); i++) { + if (clock_hz >= steps[i]) { + clock = clocks[i]; + return; + } + } + + clock = SPI_CLK_100KHZ; +} + +SPIClass::SPIClass(spi_t spi_dev) +{ + /* Check if default SPI interface is valid */ + BUILD_BUG_ON(ARDUINO_SPI_INTERFACE >= SPI_NUMOF); + this->spi_dev = spi_dev; + this->settings = SPISettings(); + this->is_transaction = false; + rmutex_init(&this->mut); +} + +void SPIClass::beginTransaction(SPISettings settings) +{ + rmutex_lock(&mut); + /* Call spi_acquire first to prevent data races */ + int retval = spi_acquire(spi_dev, SPI_CS_UNDEF, + settings.mode, settings.clock); + /* No support for exceptions (at least on AVR), resort to assert() */ + assert(retval == SPI_OK); + is_transaction = true; +} + +void SPIClass::endTransaction() +{ + spi_release(spi_dev); + is_transaction = false; + rmutex_unlock(&mut); +} + +void SPIClass::transfer(void *buf, size_t count) +{ + rmutex_lock(&mut); + + if (!is_transaction) { + int retval = spi_acquire(spi_dev, SPI_CS_UNDEF, + settings.mode, settings.clock); + /* No support for exceptions (at least on AVR), resort to assert() */ + assert(retval == SPI_OK); + } + spi_transfer_bytes(spi_dev, SPI_CS_UNDEF, false, buf, buf, count); + if (!is_transaction) { + spi_release(spi_dev); + } + + rmutex_unlock(&mut); +} + +void SPIClass::setBitOrder(uint8_t order) +{ + assert(order == MSBFIRST); +} + +void SPIClass::setDataMode(uint8_t dataMode) +{ + switch(dataMode) { + default: + case SPI_MODE0: + settings.mode = SPI_MODE_0; + break; + case SPI_MODE1: + settings.mode = SPI_MODE_1; + break; + case SPI_MODE2: + settings.mode = SPI_MODE_2; + break; + case SPI_MODE3: + settings.mode = SPI_MODE_3; + break; + } +} + +void SPIClass::setClockDivider(uint8_t divider) +{ + static const spi_clk_t clocks[] = { + SPI_CLK_5MHZ, SPI_CLK_1MHZ, SPI_CLK_400KHZ, SPI_CLK_100KHZ + }; + + assert(divider < ARRAY_SIZE(clocks)); + settings.clock = clocks[divider]; +} + +SPIClass SPI(SPI_DEV(ARDUINO_SPI_INTERFACE)); + +#else /* MODULE_PERIPH_SPI */ +typedef int dont_be_pedantic; +#endif /* MODULE_PERIPH_SPI */ diff --git a/sys/arduino/include/SPI.h b/sys/arduino/include/SPI.h new file mode 100644 index 0000000000..646050ebc2 --- /dev/null +++ b/sys/arduino/include/SPI.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_arduino + * @{ + * + * @file + * @brief Wrapper to access the definition of the Arduino 'SPI' interface + * + * @author Marian Buschsieweke + */ + +#ifndef SPI_H +#define SPI_H + +#ifndef MODULE_PERIPH_SPI +#error "No SPI support on your board" +#endif + +#ifdef __cplusplus +#include "spiport.hpp" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ARDUINO_SPI_INTERFACE +#define ARDUINO_SPI_INTERFACE 0 /**< Number of the SPI dev to make available to Arduino code */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SPI_H */ + +/** @} */ diff --git a/sys/arduino/include/spiport.hpp b/sys/arduino/include/spiport.hpp new file mode 100644 index 0000000000..e8cc58a7d7 --- /dev/null +++ b/sys/arduino/include/spiport.hpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_arduino_api + * @{ + * + * @file + * @brief Definition of the Arduino 'SPI' interface + * + * @author Marian Buschsieweke + */ + +#ifndef SPIPORT_H +#define SPIPORT_H + +#include "arduino_board.h" +#include "byteorder.h" +#include "periph/spi.h" +#include "rmutex.h" + +/** + * @name Arduino compatible SPI modes + * @{ + */ +#define SPI_MODE0 (0) /**< CPOL=0, CPHA=0 */ +#define SPI_MODE1 (1) /**< CPOL=0, CPHA=1 */ +#define SPI_MODE2 (2) /**< CPOL=1, CPHA=0 */ +#define SPI_MODE3 (3) /**< CPOL=1, CPHA=1 */ +/** @} */ + +/** + * @name Arduino compatible SPI frequency selection via clock divider + * + * This API assumes the library was targeting a 16 MHz Arduino. It will choose + * the SPI clock frequency matching the requested frequency best, but never + * a frequency greater than the one it would have on Arduinos. + * @{ + */ +#define SPI_CLOCK_DIV2 (0) /**< Best match for 8 MHz: 5 MHz */ +#define SPI_CLOCK_DIV4 (1) /**< Best match for 4 MHz: 1 MHz */ +#define SPI_CLOCK_DIV8 (1) /**< Best match for 2 MHz: 1 MHz */ +#define SPI_CLOCK_DIV16 (1) /**< Best match for 1 MHz: 1 MHz */ +#define SPI_CLOCK_DIV32 (2) /**< Best match for 500 kHz: 400 kHz */ +#define SPI_CLOCK_DIV64 (3) /**< Best match for 250 kHZ: 100 kHz */ +#define SPI_CLOCK_DIV128 (3) /**< Best match for 125 kHz: 100 kHz */ +/** @} */ + +/** + * @name Arduino compatible bit order values for SPI + * @{ + */ +#define MSBFIRST (1) /**< most significat bit first */ +/** @} */ + +/** + * @brief Arduino SPI configuration interface + */ +class SPISettings { +private: + spi_mode_t mode; + spi_clk_t clock; + +public: + /** + * @brief Create a new SPI settings instance + * + * @param clock_hz SPI clock in Hz to use + * @param bitOrder Has to be `MSBFIST`, for compatibility only + * @param dataMode SPI mode to use + * + * The RIOT SPI clock frequency best matching @p clock will be chosen, + * but the frequency will never by greater than what is given in @p clock, + * unless @p clock_hz is is lower than 100kHz, which is the lowest clock + * frequency that will be used. + */ + SPISettings(uint32_t clock_hz, uint8_t bitOrder, uint8_t dataMode); + + /** + * @brief Create a new SPI settings instance with default settings + */ + SPISettings() : SPISettings(4000000, MSBFIRST, SPI_MODE0) { } + + friend class SPIClass; +}; + +/** + * @brief Arduino SPI interface + * + * @warning Wrap all SPI transfers in `SPI.beginTransaction()` and `SPI.endTransaction()` + * + * The official Arduino SPI-API allows to use SPI transfers without having to + * call `SPI.beginTransaction()`, but discourages to do so. The RIOT API does + * not provide this feature, instead a call to `SPI.beginTransaction()` is + * mandatory. However, most Arduino code already does this and the remaining + * code should be fixed anyway. + */ +class SPIClass { +private: + spi_t spi_dev; + SPISettings settings; + bool is_transaction; + rmutex_t mut; + +public: + /** + * @brief Create a new SPI interface instance + * @param spi_dev The RIOT SPI device to use under the hood + */ + explicit SPIClass(spi_t spi_dev); + + /** + * @brief Create a new SPI interface instance for SPI device 0 + * @param uc_pinMISO Ignored, for compatibility only + * @param uc_pinSCK Ignored, for compatibility only + * @param uc_pinMOSI Ignored, for compatibility only + * @param uc_pinSS Ignored, for compatibility only + * @param uc_mux Ignored, for compatibility only + */ + SPIClass(uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, + uint8_t uc_pinSS, uint8_t uc_mux) : SPIClass(SPI_DEV(0)) + { + (void)uc_pinMISO; + (void)uc_pinSCK; + (void)uc_pinMOSI; + (void)uc_pinSS; + (void)uc_mux; + } + + /** + * @brief Transfer a single byte of data + * @param[in] data Byte to send + * @return The received byte + */ + uint8_t transfer(uint8_t data) + { + transfer(&data, sizeof(data)); + return data; + } + + /** + * @brief Transfer two bytes of data + * @param[in] data The two bytes to send + * @return The received two bytes + * + * Arduino is sending the most significant byte first, if the SPI interface + * is configured to send the most significant bit first. If the least + * significant bit is send first, Arduino will also send the least + * significant byte first. + * + * This wrapper currently only supports sending the most significant bit + * first over the wire, so this function will also always send the most + * significant byte first. + */ + uint16_t transfer16(uint16_t data) + { + data = htons(data); + transfer(&data, sizeof(data)); + return ntohs(data); + } + + /** + * @brief Transfer data + * @param[inout] buf Buffer containing the data to send, received + * data will be written here + * @param[in] count Number of bytes to send + */ + void transfer(void *buf, size_t count); + + /** + * @brief Doesn't do anything, for compatibility only + */ + void begin() { } + + /** + * @brief Doesn't do anything, for compatibility only + */ + void end() { } + + /** + * @brief Acquires the SPI interface and applies the given settings + * @param[in] settings Settings to apply + */ + void beginTransaction(SPISettings settings); + + /** + * @brief Releases the SPI interface + */ + void endTransaction(); + + /** + * @brief Sets the bit order to the given value + * + * @details Currently only most significant bit first is supported, as + * practically no hardware exists using lsbfirst. An assertion + * is triggered if lsbfirst is requested. + * @deprecated This function is deprecated in the official Arduino API, + * so it is a good idea to not use it. In RIOT, this function + * is not yet scheduled for removal to allow using Arduino + * libraries using it. + */ + void setBitOrder(uint8_t order); + + /** + * @brief Sets the SPI mode (clock phase and polarity) + * + * @deprecated This function is deprecated in the official Arduino API, + * so it is a good idea to not use it. In RIOT, this function + * is not yet scheduled for removal to allow using Arduino + * libraries using it. + */ + void setDataMode(uint8_t mode); + + /** + * @brief Sets the SPI clock in an archaic manner + * + * @deprecated This function is deprecated in the official Arduino API, + * so it is a good idea to not use it. In RIOT, this function + * is not yet scheduled for removal to allow using Arduino + * libraries using it. + */ + void setClockDivider(uint8_t divider); +}; + +/** + * @brief: Instance of the SPI interface as required by the Arduino API + */ +extern SPIClass SPI; + +#endif /* SPIPORT_H */ +/** @} */