mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
sys/arduino: Added SPI interface
Added an Arduino compatible SPI API on top of RIOT's SPI API.
This commit is contained in:
parent
00926221fd
commit
cc50da690b
@ -1,5 +1,6 @@
|
||||
ifneq (,$(filter arduino,$(USEMODULE)))
|
||||
FEATURES_OPTIONAL += periph_i2c
|
||||
FEATURES_OPTIONAL += periph_spi
|
||||
endif
|
||||
|
||||
ifneq (,$(filter eepreg,$(USEMODULE)))
|
||||
|
149
sys/arduino/SPI.cpp
Normal file
149
sys/arduino/SPI.cpp
Normal file
@ -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 <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#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 */
|
44
sys/arduino/include/SPI.h
Normal file
44
sys/arduino/include/SPI.h
Normal file
@ -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 <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#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 */
|
||||
|
||||
/** @} */
|
236
sys/arduino/include/spiport.hpp
Normal file
236
sys/arduino/include/spiport.hpp
Normal file
@ -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 <marian.buschsieweke@ovgu.de>
|
||||
*/
|
||||
|
||||
#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 */
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user