ArduinoCore-samd/libraries/SPI/SPI.cpp
2018-08-28 17:09:02 -04:00

414 lines
12 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
*/
#include "SPI.h"
#include <Arduino.h>
#include <wiring_private.h>
#include <assert.h>
#define SPI_IMODE_NONE 0
#define SPI_IMODE_EXTINT 1
#define SPI_IMODE_GLOBAL 2
const SPISettings DEFAULT_SPI_SETTINGS = SPISettings();
SPIClass::SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI,
SercomSpiTXPad PadTx, SercomRXPad PadRx, int8_t dmaChannelRx, int8_t dmaChannelTx)
{
initialized = false;
assert(p_sercom != NULL);
_p_sercom = p_sercom;
// pins
_uc_pinMiso = uc_pinMISO;
_uc_pinSCK = uc_pinSCK;
_uc_pinMosi = uc_pinMOSI;
_dmaChannelRx = dmaChannelRx;
_dmaChannelTx = dmaChannelTx;
// SERCOM pads
_padTx=PadTx;
_padRx=PadRx;
}
void SPIClass::begin()
{
init();
// PIO init
pinPeripheral(_uc_pinMiso, g_APinDescription[_uc_pinMiso].ulPinType);
pinPeripheral(_uc_pinSCK, g_APinDescription[_uc_pinSCK].ulPinType);
pinPeripheral(_uc_pinMosi, g_APinDescription[_uc_pinMosi].ulPinType);
if(_dmaChannelRx > -1 && _dmaChannelTx > -1){
descrx = &_descriptor[_dmaChannelRx];
desctx = &_descriptor[_dmaChannelTx];
descrx->BTCTRL.bit.VALID = true;
descrx->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE;
descrx->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
descrx->BTCTRL.bit.BEATSIZE = DMA_BEAT_SIZE_BYTE;
descrx->BTCTRL.bit.SRCINC = false;
descrx->BTCTRL.bit.DSTINC = true;
descrx->BTCTRL.bit.STEPSEL = DMA_STEPSEL_DST;
descrx->BTCTRL.bit.STEPSIZE = DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
descrx->BTCNT.reg = 0;
descrx->SRCADDR.reg = (uint32_t)0;
desctx->BTCTRL.bit.VALID = true;
desctx->BTCTRL.bit.EVOSEL = DMA_EVENT_OUTPUT_DISABLE;
desctx->BTCTRL.bit.BLOCKACT = DMA_BLOCK_ACTION_NOACT;
desctx->BTCTRL.bit.BEATSIZE = DMA_BEAT_SIZE_BYTE;
desctx->BTCTRL.bit.SRCINC = true;
desctx->BTCTRL.bit.DSTINC = false;
desctx->BTCTRL.bit.STEPSEL = DMA_STEPSEL_DST;
desctx->BTCTRL.bit.STEPSIZE = DMA_ADDRESS_INCREMENT_STEP_SIZE_1;
desctx->BTCNT.reg = 0;
desctx->SRCADDR.reg = (uint32_t)0;
#ifdef __SAMD51__
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.ENABLE = 0;
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.SWRST = 1;
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.ENABLE = 0;
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.SWRST = 1;
DMAC->Channel[_dmaChannelRx].CHPRILVL.bit.PRILVL = 0;
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.TRIGACT = DMA_TRIGGER_ACTON_BEAT;
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.BURSTLEN = DMAC_CHCTRLA_BURSTLEN_SINGLE_Val; // Single-beat burst length
DMAC->Channel[_dmaChannelTx].CHPRILVL.bit.PRILVL = 0;
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.TRIGACT = DMA_TRIGGER_ACTON_BEAT;
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.BURSTLEN = DMAC_CHCTRLA_BURSTLEN_SINGLE_Val; // Single-beat burst length
DMAC->SWTRIGCTRL.reg &= ~(1UL << _dmaChannelTx);
DMAC->SWTRIGCTRL.reg &= ~(1UL << _dmaChannelRx);
#else
DMAC->CHID.bit.ID = _dmaChannelRx;
DMAC->CHCTRLA.bit.ENABLE = 0;
DMAC->CHCTRLA.bit.SWRST = 1;
DMAC->CHCTRLB.bit.LVL = 0;
DMAC->CHCTRLB.bit.TRIGACT = DMA_TRIGGER_ACTON_BEAT;
DMAC->CHID.bit.ID = _dmaChannelTx;
DMAC->CHCTRLA.bit.ENABLE = 0;
DMAC->CHCTRLA.bit.SWRST = 1;
DMAC->CHCTRLB.bit.LVL = 0;
DMAC->CHCTRLB.bit.TRIGACT = DMA_TRIGGER_ACTON_BEAT;
#endif
_p_sercom->initSPIDMA(_dmaChannelRx, _dmaChannelTx, descrx, desctx);
}
config(DEFAULT_SPI_SETTINGS);
}
void SPIClass::init()
{
if (initialized)
return;
interruptMode = SPI_IMODE_NONE;
interruptSave = 0;
interruptMask = 0;
initialized = true;
}
void SPIClass::config(SPISettings settings)
{
_p_sercom->disableSPI();
_p_sercom->initSPI(_padTx, _padRx, SPI_CHAR_SIZE_8_BITS, settings.bitOrder);
_p_sercom->initSPIClock(settings.dataMode, settings.clockFreq);
_p_sercom->enableSPI();
}
void SPIClass::end()
{
_p_sercom->resetSPI();
initialized = false;
}
#ifndef interruptsStatus
#define interruptsStatus() __interruptsStatus()
static inline unsigned char __interruptsStatus(void) __attribute__((always_inline, unused));
static inline unsigned char __interruptsStatus(void)
{
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0497a/CHDBIBGJ.html
return (__get_PRIMASK() ? 0 : 1);
}
#endif
void SPIClass::usingInterrupt(int interruptNumber)
{
if ((interruptNumber == NOT_AN_INTERRUPT) || (interruptNumber == EXTERNAL_INT_NMI))
return;
uint8_t irestore = interruptsStatus();
noInterrupts();
if (interruptNumber >= EXTERNAL_NUM_INTERRUPTS)
interruptMode = SPI_IMODE_GLOBAL;
else
{
interruptMode |= SPI_IMODE_EXTINT;
interruptMask |= (1 << interruptNumber);
}
if (irestore)
interrupts();
}
void SPIClass::beginTransaction(SPISettings settings)
{
if (interruptMode != SPI_IMODE_NONE)
{
if (interruptMode & SPI_IMODE_GLOBAL)
{
interruptSave = interruptsStatus();
noInterrupts();
}
else if (interruptMode & SPI_IMODE_EXTINT)
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT(interruptMask);
}
config(settings);
}
void SPIClass::endTransaction(void)
{
if (interruptMode != SPI_IMODE_NONE)
{
if (interruptMode & SPI_IMODE_GLOBAL)
{
if (interruptSave)
interrupts();
}
else if (interruptMode & SPI_IMODE_EXTINT)
EIC->INTENSET.reg = EIC_INTENSET_EXTINT(interruptMask);
}
}
void SPIClass::setBitOrder(BitOrder order)
{
if (order == LSBFIRST) {
_p_sercom->setDataOrderSPI(LSB_FIRST);
} else {
_p_sercom->setDataOrderSPI(MSB_FIRST);
}
}
void SPIClass::setDataMode(uint8_t mode)
{
switch (mode)
{
case SPI_MODE0:
_p_sercom->setClockModeSPI(SERCOM_SPI_MODE_0);
break;
case SPI_MODE1:
_p_sercom->setClockModeSPI(SERCOM_SPI_MODE_1);
break;
case SPI_MODE2:
_p_sercom->setClockModeSPI(SERCOM_SPI_MODE_2);
break;
case SPI_MODE3:
_p_sercom->setClockModeSPI(SERCOM_SPI_MODE_3);
break;
default:
break;
}
}
void SPIClass::setClockDivider(uint8_t div)
{
if (div < SPI_MIN_CLOCK_DIVIDER) {
_p_sercom->setBaudrateSPI(SPI_MIN_CLOCK_DIVIDER);
} else {
_p_sercom->setBaudrateSPI(div);
}
}
byte SPIClass::transfer(uint8_t data)
{
return _p_sercom->transferDataSPI(data);
}
uint16_t SPIClass::transfer16(uint16_t data) {
union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } t;
t.val = data;
if (_p_sercom->getDataOrderSPI() == LSB_FIRST) {
t.lsb = transfer(t.lsb);
t.msb = transfer(t.msb);
} else {
t.msb = transfer(t.msb);
t.lsb = transfer(t.lsb);
}
return t.val;
}
void SPIClass::transfer(void *rx, void *tx, size_t count)
{
if(count > 2 && count < 65536 && _dmaChannelRx > -1 && _dmaChannelTx > -1){
//use a synchronous DMA transfer
descrx->BTCTRL.bit.VALID = true;
descrx->DSTADDR.reg = (uint32_t)rx + count;
descrx->BTCNT.reg = count;
desctx->BTCTRL.bit.VALID = true;
desctx->SRCADDR.reg = (uint32_t)tx + count;
desctx->BTCNT.reg = count;
#ifdef __SAMD51__
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.ENABLE = 1;
if(rx != NULL)
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.ENABLE = 1;
#else
DMAC->CHID.bit.ID = _dmaChannelTx;
DMAC->CHCTRLA.bit.ENABLE = 1;
DMAC->CHID.bit.ID = _dmaChannelRx;
DMAC->CHCTRLA.bit.ENABLE = 1;
#endif
//wait for the transfer to finish
while(_writeback[_dmaChannelTx].BTCTRL.bit.VALID || !_p_sercom->isDataRegisterEmptySPI());
while(!_p_sercom->isTransmitCompleteSPI());
}
else{
//use a normal transfer
uint8_t *rxBuffer = reinterpret_cast<uint8_t *>(rx);
uint8_t *txBuffer = reinterpret_cast<uint8_t *>(tx);
for (size_t i=0; i<count; i++) {
*rxBuffer++ = transfer(*txBuffer++);
}
}
}
//returns true if the transfer could be started, false otherwise
bool SPIClass::transferNonBlocking(void *rx, void *tx, size_t count)
{
if(count < 65536 && _dmaChannelRx > -1 && _dmaChannelTx > -1){
//previous transfer has not finished
if(_writeback[_dmaChannelTx].BTCTRL.bit.VALID || !_p_sercom->isDataRegisterEmptySPI() ||
!_p_sercom->isTransmitCompleteSPI()){
return false;
}
//use a synchronous DMA transfer
descrx->BTCTRL.bit.VALID = true;
descrx->DSTADDR.reg = (uint32_t)rx + count;
descrx->BTCNT.reg = count;
desctx->BTCTRL.bit.VALID = true;
desctx->SRCADDR.reg = (uint32_t)tx + count;
desctx->BTCNT.reg = count;
#ifdef __SAMD51__
DMAC->Channel[_dmaChannelTx].CHCTRLA.bit.ENABLE = 1;
if(rx != NULL)
DMAC->Channel[_dmaChannelRx].CHCTRLA.bit.ENABLE = 1;
#else
DMAC->CHID.bit.ID = _dmaChannelTx;
DMAC->CHCTRLA.bit.ENABLE = 1;
DMAC->CHID.bit.ID = _dmaChannelRx;
DMAC->CHCTRLA.bit.ENABLE = 1;
#endif
return true;
}
else{
return false;
}
}
void SPIClass::transfer(void *buf, size_t count){
transfer(buf, buf, count);
}
void SPIClass::attachInterrupt() {
// Should be enableInterrupt()
}
void SPIClass::detachInterrupt() {
// Should be disableInterrupt()
}
#if SPI_INTERFACES_COUNT > 0
/* In case new variant doesn't define these macros,
* we put here the ones for Arduino Zero.
*
* These values should be different on some variants!
*
* The SPI PAD values can be found in cores/arduino/SERCOM.h:
* - SercomSpiTXPad
* - SercomRXPad
*/
#ifndef PERIPH_SPI
#define PERIPH_SPI sercom4
#define PAD_SPI_TX SPI_PAD_2_SCK_3
#define PAD_SPI_RX SERCOM_RX_PAD_0
#endif // PERIPH_SPI
#ifdef SPI_HAS_DMA
SPIClass SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX, SPI_DMA_CHANNEL_RX, SPI_DMA_CHANNEL_TX);
#else
SPIClass SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX);
#endif
#endif
#if SPI_INTERFACES_COUNT > 1
#ifdef SPI1_HAS_DMA
SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX, SPI1_DMA_CHANNEL_RX, SPI1_DMA_CHANNEL_TX);
#else
SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX);
#endif
#endif
#if SPI_INTERFACES_COUNT > 2
#ifdef SPI2_HAS_DMA
SPIClass SPI2(&PERIPH_SPI2, PIN_SPI2_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX, SPI2_DMA_CHANNEL_RX, SPI2_DMA_CHANNEL_TX);
#else
SPIClass SPI2(&PERIPH_SPI2, PIN_SPI1_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX);
#endif
#endif
#if SPI_INTERFACES_COUNT > 3
#ifdef SPI3_HAS_DMA
SPIClass SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX, SPI3_DMA_CHANNEL_RX, SPI3_DMA_CHANNEL_TX);
#else
SPIClass SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX);
#endif
#endif
#if SPI_INTERFACES_COUNT > 4
#ifdef SPI4_HAS_DMA
SPIClass SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX, SPI4_DMA_CHANNEL_RX, SPI4_DMA_CHANNEL_TX);
#else
SPIClass SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX);
#endif
#endif
#if SPI_INTERFACES_COUNT > 5
#ifdef SPI5_HAS_DMA
SPIClass SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX, SPI5_DMA_CHANNEL_RX, SPI5_DMA_CHANNEL_TX);
#else
SPIClass SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX);
#endif
#endif