/* * 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 #include #include #define SPI_IMODE_NONE 0 #define SPI_IMODE_EXTINT 1 #define SPI_IMODE_GLOBAL 2 //const SPISettings DEFAULT_SPI_SETTINGS = SPISettings(); static inline SercomDataOrder getBitOrder(SPISettings& settings) { return (settings.getBitOrder() == MSBFIRST ? MSB_FIRST : LSB_FIRST); } static inline SercomSpiClockMode getDataMode(SPISettings& settings) { switch (settings.getDataMode()) { case SPI_MODE0: return SERCOM_SPI_MODE_0; break; case SPI_MODE1: return SERCOM_SPI_MODE_1; break; case SPI_MODE2: return SERCOM_SPI_MODE_2; break; case SPI_MODE3: return SERCOM_SPI_MODE_3; break; default: return SERCOM_SPI_MODE_0; break; } } SPIClassSAMD::SPIClassSAMD(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, SercomSpiTXPad PadTx, SercomRXPad PadRx) : settings(0, MSBFIRST, SPI_MODE0) { initialized = false; assert(p_sercom != NULL); _p_sercom = p_sercom; // pins _uc_pinMiso = uc_pinMISO; _uc_pinSCK = uc_pinSCK; _uc_pinMosi = uc_pinMOSI; // SERCOM pads _padTx=PadTx; _padRx=PadRx; } void SPIClassSAMD::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); config(DEFAULT_SPI_SETTINGS); } void SPIClassSAMD::init() { if (initialized) return; interruptMode = SPI_IMODE_NONE; interruptSave = 0; interruptMask = 0; initialized = true; } void SPIClassSAMD::config(SPISettings settings) { if (this->settings != settings) { this->settings = settings; _p_sercom->disableSPI(); uint32_t clock_freq = settings.getClockFreq(); if (clock_freq > SERCOM_FREQ_REF/SPI_MIN_CLOCK_DIVIDER) { clock_freq = SERCOM_FREQ_REF/SPI_MIN_CLOCK_DIVIDER; } _p_sercom->initSPI(_padTx, _padRx, SPI_CHAR_SIZE_8_BITS, getBitOrder(settings)); _p_sercom->initSPIClock(getDataMode(settings), clock_freq); _p_sercom->enableSPI(); } } void SPIClassSAMD::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 SPIClassSAMD::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 << g_APinDescription[interruptNumber].ulExtInt); } if (irestore) interrupts(); } void SPIClassSAMD::notUsingInterrupt(int interruptNumber) { if ((interruptNumber == NOT_AN_INTERRUPT) || (interruptNumber == EXTERNAL_INT_NMI)) return; if (interruptMode & SPI_IMODE_GLOBAL) return; // can't go back, as there is no reference count uint8_t irestore = interruptsStatus(); noInterrupts(); interruptMask &= ~(1 << g_APinDescription[interruptNumber].ulExtInt); if (interruptMask == 0) interruptMode = SPI_IMODE_NONE; if (irestore) interrupts(); } void SPIClassSAMD::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 SPIClassSAMD::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 SPIClassSAMD::setBitOrder(BitOrder order) { if (order == LSBFIRST) { _p_sercom->setDataOrderSPI(LSB_FIRST); } else { _p_sercom->setDataOrderSPI(MSB_FIRST); } } void SPIClassSAMD::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 SPIClassSAMD::setClockDivider(uint8_t div) { if (div < SPI_MIN_CLOCK_DIVIDER) { _p_sercom->setBaudrateSPI(SPI_MIN_CLOCK_DIVIDER); } else { _p_sercom->setBaudrateSPI(div); } } byte SPIClassSAMD::transfer(uint8_t data) { return _p_sercom->transferDataSPI(data); } uint16_t SPIClassSAMD::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 SPIClassSAMD::transfer(void *buf, size_t count) { uint8_t *buffer = reinterpret_cast(buf); for (size_t i=0; i 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 SPIClassSAMD SPI (&PERIPH_SPI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_MOSI, PAD_SPI_TX, PAD_SPI_RX); #endif #if SPI_INTERFACES_COUNT > 1 SPIClassSAMD SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX); #endif #if SPI_INTERFACES_COUNT > 2 SPIClassSAMD SPI2(&PERIPH_SPI2, PIN_SPI2_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX); #endif #if SPI_INTERFACES_COUNT > 3 SPIClassSAMD SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX); #endif #if SPI_INTERFACES_COUNT > 4 SPIClassSAMD SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX); #endif #if SPI_INTERFACES_COUNT > 5 SPIClassSAMD SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX); #endif