ArduinoCore-samd/libraries/SPI/SPI.h

214 lines
6.7 KiB
C++

/*
* SPI Master library for Arduino Zero.
* Copyright (c) 2015 Arduino LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED
#include <Arduino.h>
#include <Adafruit_ZeroDMA.h>
// SPI_HAS_TRANSACTION means SPI has
// - beginTransaction()
// - endTransaction()
// - usingInterrupt()
// - SPISetting(clock, bitOrder, dataMode)
#define SPI_HAS_TRANSACTION 1
// SPI_HAS_NOTUSINGINTERRUPT means that SPI has notUsingInterrupt() method
#define SPI_HAS_NOTUSINGINTERRUPT 1
#define SPI_MODE0 0x02
#define SPI_MODE1 0x00
#define SPI_MODE2 0x03
#define SPI_MODE3 0x01
#if defined(__SAMD51__)
// SAMD51 has configurable MAX_SPI, else use peripheral clock default.
// Update: changing MAX_SPI via compiler flags is DEPRECATED, because
// this affects ALL SPI peripherals including some that should NOT be
// changed (e.g. anything using SD card). Use the setClockSource()
// function instead. This is left here for compatibility with interim code.
#if !defined(MAX_SPI)
#define MAX_SPI 24000000
#endif
#define SPI_MIN_CLOCK_DIVIDER 1
#else
// The datasheet specifies a typical SPI SCK period (tSCK) of 42 ns,
// see "Table 36-48. SPI Timing Characteristics and Requirements",
// which translates into a maximum SPI clock of 23.8 MHz.
// We'll permit use of 24 MHz SPI even though this is slightly out
// of spec. Given how clock dividers work, the next "sensible"
// threshold would be a substantial drop down to 12 MHz.
#if !defined(MAX_SPI)
#define MAX_SPI 24000000
#endif
#define SPI_MIN_CLOCK_DIVIDER (uint8_t)(1 + ((F_CPU - 1) / MAX_SPI))
#endif
class SPISettings {
public:
SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
if (__builtin_constant_p(clock)) {
init_AlwaysInline(clock, bitOrder, dataMode);
} else {
init_MightInline(clock, bitOrder, dataMode);
}
}
// Default speed set to 4MHz, SPI mode set to MODE 0 and Bit order set to MSB first.
SPISettings() { init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); }
private:
void init_MightInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) {
init_AlwaysInline(clock, bitOrder, dataMode);
}
void init_AlwaysInline(uint32_t clock, BitOrder bitOrder, uint8_t dataMode) __attribute__((__always_inline__)) {
#if defined(__SAMD51__)
this->clockFreq = clock; // Clipping handled in SERCOM.cpp
#else
this->clockFreq = clock >= MAX_SPI ? MAX_SPI : clock;
#endif
this->bitOrder = (bitOrder == MSBFIRST ? MSB_FIRST : LSB_FIRST);
switch (dataMode)
{
case SPI_MODE0:
this->dataMode = SERCOM_SPI_MODE_0; break;
case SPI_MODE1:
this->dataMode = SERCOM_SPI_MODE_1; break;
case SPI_MODE2:
this->dataMode = SERCOM_SPI_MODE_2; break;
case SPI_MODE3:
this->dataMode = SERCOM_SPI_MODE_3; break;
default:
this->dataMode = SERCOM_SPI_MODE_0; break;
}
}
uint32_t clockFreq;
SercomSpiClockMode dataMode;
SercomDataOrder bitOrder;
friend class SPIClass;
};
class SPIClass {
public:
SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, SercomSpiTXPad, SercomRXPad);
byte transfer(uint8_t data);
uint16_t transfer16(uint16_t data);
void transfer(void *buf, size_t count);
void transfer(const void* txbuf, void* rxbuf, size_t count,
bool block = true);
void waitForTransfer(void);
bool isBusy(void);
// Transaction Functions
void usingInterrupt(int interruptNumber);
void notUsingInterrupt(int interruptNumber);
void beginTransaction(SPISettings settings);
void endTransaction(void);
// SPI Configuration methods
void attachInterrupt();
void detachInterrupt();
void begin();
void end();
void setBitOrder(BitOrder order);
void setDataMode(uint8_t uc_mode);
void setClockDivider(uint8_t uc_div);
// SERCOM lookup functions are available on both SAMD51 and 21.
volatile uint32_t *getDataRegister(void);
int getDMAC_ID_TX(void);
int getDMAC_ID_RX(void);
uint8_t getSercomIndex(void) { return _p_sercom->getSercomIndex(); };
#if defined(__SAMD51__)
// SERCOM clock source override is available only on SAMD51.
void setClockSource(SercomClockSource clk);
#else
// On SAMD21, this compiles to nothing, so user code doesn't need to
// check and conditionally compile lines for different architectures.
void setClockSource(SercomClockSource clk) { (void)clk; };
#endif // end __SAMD51__
private:
void config(SPISettings settings);
SERCOM *_p_sercom;
uint8_t _uc_pinMiso;
uint8_t _uc_pinMosi;
uint8_t _uc_pinSCK;
SercomSpiTXPad _padTx;
SercomRXPad _padRx;
bool initialized;
uint8_t interruptMode;
char interruptSave;
uint32_t interruptMask;
// transfer(txbuf, rxbuf, count, block) uses DMA when possible
Adafruit_ZeroDMA readChannel;
Adafruit_ZeroDMA writeChannel;
DmacDescriptor *firstReadDescriptor = NULL; // List entry point
DmacDescriptor *firstWriteDescriptor = NULL;
DmacDescriptor *extraReadDescriptors = NULL; // Add'l descriptors
DmacDescriptor *extraWriteDescriptors = NULL;
bool use_dma = false; // true on successful alloc
volatile bool dma_busy = false;
void dmaAllocate(void);
static void dmaCallback(Adafruit_ZeroDMA *dma);
};
#if SPI_INTERFACES_COUNT > 0
extern SPIClass SPI;
#endif
#if SPI_INTERFACES_COUNT > 1
extern SPIClass SPI1;
#endif
#if SPI_INTERFACES_COUNT > 2
extern SPIClass SPI2;
#endif
#if SPI_INTERFACES_COUNT > 3
extern SPIClass SPI3;
#endif
#if SPI_INTERFACES_COUNT > 4
extern SPIClass SPI4;
#endif
#if SPI_INTERFACES_COUNT > 5
extern SPIClass SPI5;
#endif
// For compatibility with sketches designed for AVR @ 16 MHz
// New programs should use SPI.beginTransaction to set the SPI clock
#define SPI_CLOCK_DIV2 (MAX_SPI * 2 / 8000000)
#define SPI_CLOCK_DIV4 (MAX_SPI * 2 / 4000000)
#define SPI_CLOCK_DIV8 (MAX_SPI * 2 / 2000000)
#define SPI_CLOCK_DIV16 (MAX_SPI * 2 / 1000000)
#define SPI_CLOCK_DIV32 (MAX_SPI * 2 / 500000)
#define SPI_CLOCK_DIV64 (MAX_SPI * 2 / 250000)
#define SPI_CLOCK_DIV128 (MAX_SPI * 2 / 125000)
#endif