You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1190 lines
34 KiB
1190 lines
34 KiB
#ifndef DMASPI_H
|
|
#define DMASPI_H
|
|
|
|
#include <Arduino.h>
|
|
#include <util/atomic.h>
|
|
|
|
#if(!defined(__arm__) && defined(TEENSYDUINO))
|
|
#error This library is for teensyduino 1.21 on Teensy 3.0, 3.1 and Teensy LC only.
|
|
#endif
|
|
|
|
#include <SPI.h>
|
|
#include "DMAChannel.h"
|
|
#include <core_pins.h>
|
|
|
|
//#include <core_cm7.h>
|
|
#include <arm_math.h>
|
|
|
|
//#define DEBUG_DMASPI 1
|
|
|
|
|
|
/** \brief Specifies the desired CS suppression
|
|
**/
|
|
enum TransferType
|
|
{
|
|
NORMAL, //*< The transfer will use CS at beginning and end **/
|
|
NO_START_CS, //*< Skip the CS activation at the start **/
|
|
NO_END_CS //*< SKip the CS deactivation at the end **/
|
|
};
|
|
|
|
/** \brief An abstract base class that provides an interface for chip select classes.
|
|
**/
|
|
class AbstractChipSelect
|
|
{
|
|
public:
|
|
/** \brief Called to select a chip. The implementing class can do other things as well.
|
|
**/
|
|
virtual void select(TransferType transferType = TransferType::NORMAL) = 0;
|
|
|
|
/** \brief Called to deselect a chip. The implementing class can do other things as well.
|
|
**/
|
|
virtual void deselect(TransferType transferType = TransferType::NORMAL) = 0;
|
|
|
|
/** \brief the virtual destructor needed to inherit from this class **/
|
|
virtual ~AbstractChipSelect() {}
|
|
};
|
|
|
|
|
|
/** \brief "do nothing" chip select class **/
|
|
class DummyChipSelect : public AbstractChipSelect
|
|
{
|
|
void select(TransferType transferType = TransferType::NORMAL) override {}
|
|
|
|
void deselect(TransferType transferType = TransferType::NORMAL) override {}
|
|
};
|
|
|
|
/** \brief "do nothing" chip select class that
|
|
* outputs a message through Serial when something happens
|
|
**/
|
|
class DebugChipSelect : public AbstractChipSelect
|
|
{
|
|
void select(TransferType transferType = TransferType::NORMAL) override {Serial.println("Debug CS: select()");}
|
|
void deselect(TransferType transferType = TransferType::NORMAL) override {Serial.println("Debug CS: deselect()");}
|
|
};
|
|
|
|
/** \brief An active low chip select class. This also configures the given pin.
|
|
* Warning: This class is hardcoded to manage a transaction on SPI (SPI0, that is).
|
|
* If you want to use SPI1: Use AbstractChipSelect1 (see below)
|
|
* If you want to use SPI2: Create AbstractChipSelect2 (adapt the implementation accordingly).
|
|
* Something more flexible is on the way.
|
|
**/
|
|
class ActiveLowChipSelect : public AbstractChipSelect
|
|
{
|
|
public:
|
|
/** Configures a chip select pin for OUTPUT mode,
|
|
* manages the chip selection and a corresponding SPI transaction
|
|
*
|
|
* The chip select pin is asserted \e after the SPI settings are applied
|
|
* and deasserted before the SPI transaction ends.
|
|
* \param pin the CS pin to use
|
|
* \param settings which SPI settings to apply when the chip is selected
|
|
**/
|
|
ActiveLowChipSelect(const unsigned int& pin, const SPISettings& settings)
|
|
: pin_(pin),
|
|
settings_(settings)
|
|
{
|
|
pinMode(pin, OUTPUT);
|
|
digitalWriteFast(pin, 1);
|
|
}
|
|
|
|
/** \brief begins an SPI transaction selects the chip (sets the pin to low) and
|
|
**/
|
|
void select(TransferType transferType = TransferType::NORMAL) override
|
|
{
|
|
SPI.beginTransaction(settings_);
|
|
if (transferType == TransferType::NO_START_CS) {
|
|
return;
|
|
}
|
|
digitalWriteFast(pin_, 0);
|
|
}
|
|
|
|
/** \brief deselects the chip (sets the pin to high) and ends the SPI transaction
|
|
**/
|
|
void deselect(TransferType transferType = TransferType::NORMAL) override
|
|
{
|
|
if (transferType == TransferType::NO_END_CS) {
|
|
} else {
|
|
digitalWriteFast(pin_, 1);
|
|
}
|
|
SPI.endTransaction();
|
|
}
|
|
private:
|
|
const unsigned int pin_;
|
|
const SPISettings settings_;
|
|
|
|
};
|
|
|
|
#if defined(__MK66FX1M0__)
|
|
class ActiveLowChipSelect1 : public AbstractChipSelect
|
|
{
|
|
public:
|
|
/** Equivalent to AbstractChipSelect, but for SPI1.
|
|
**/
|
|
ActiveLowChipSelect1(const unsigned int& pin, const SPISettings& settings)
|
|
: pin_(pin),
|
|
settings_(settings)
|
|
{
|
|
pinMode(pin, OUTPUT);
|
|
digitalWriteFast(pin, 1);
|
|
}
|
|
|
|
/** \brief begins an SPI transaction selects the chip (sets the pin to low) and
|
|
**/
|
|
void select(TransferType transferType = TransferType::NORMAL) override
|
|
{
|
|
SPI1.beginTransaction(settings_);
|
|
if (transferType == TransferType::NO_START_CS) {
|
|
return;
|
|
}
|
|
digitalWriteFast(pin_, 0);
|
|
}
|
|
|
|
/** \brief deselects the chip (sets the pin to high) and ends the SPI transaction
|
|
**/
|
|
void deselect(TransferType transferType = TransferType::NORMAL) override
|
|
{
|
|
if (transferType == TransferType::NO_END_CS) {
|
|
} else {
|
|
digitalWriteFast(pin_, 1);
|
|
}
|
|
SPI1.endTransaction();
|
|
}
|
|
private:
|
|
const unsigned int pin_;
|
|
const SPISettings settings_;
|
|
|
|
};
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(DEBUG_DMASPI)
|
|
#define DMASPI_PRINT(x) do {Serial.printf x ; Serial.flush();} while (0);
|
|
#else
|
|
#define DMASPI_PRINT(x) do {} while (0);
|
|
#endif
|
|
|
|
namespace DmaSpi
|
|
{
|
|
/** \brief describes an SPI transfer
|
|
*
|
|
* Transfers are kept in a queue (intrusive linked list) until they are processed by the DmaSpi driver.
|
|
*
|
|
**/
|
|
class Transfer
|
|
{
|
|
public:
|
|
/** \brief The Transfer's current state.
|
|
*
|
|
**/
|
|
enum State
|
|
{
|
|
idle, /**< The Transfer is idle, the DmaSpi has not seen it yet. **/
|
|
eDone, /**< The Transfer is done. **/
|
|
pending, /**< Queued, but not handled yet. **/
|
|
inProgress, /**< The DmaSpi driver is currently busy executing this Transfer. **/
|
|
error /**< An error occured. **/
|
|
};
|
|
|
|
/** \brief Creates a Transfer object.
|
|
* \param pSource pointer to the data source. If this is nullptr, the fill value is used instead.
|
|
* \param transferCount the number of SPI transfers to perform.
|
|
* \param pDest pointer to the data sink. If this is nullptr, data received from the slave will be discarded.
|
|
* \param fill if pSource is nullptr, this value is sent to the slave instead.
|
|
* \param cs pointer to a chip select object.
|
|
* If not nullptr, cs->select() is called when the Transfer is started and cs->deselect() is called when the Transfer is finished.
|
|
**/
|
|
Transfer(const uint8_t* pSource = nullptr,
|
|
const uint16_t& transferCount = 0,
|
|
volatile uint8_t* pDest = nullptr,
|
|
const uint8_t& fill = 0,
|
|
AbstractChipSelect* cs = nullptr,
|
|
TransferType transferType = TransferType::NORMAL,
|
|
uint8_t *pSourceIntermediate = nullptr,
|
|
volatile uint8_t *pDestIntermediate = nullptr
|
|
) : m_state(State::idle),
|
|
m_pSource(pSource),
|
|
m_transferCount(transferCount),
|
|
m_pDest(pDest),
|
|
m_fill(fill),
|
|
m_pNext(nullptr),
|
|
m_pSelect(cs),
|
|
m_transferType(transferType),
|
|
m_pSourceIntermediate(pSourceIntermediate),
|
|
m_pDestIntermediate(pDestIntermediate)
|
|
{
|
|
DMASPI_PRINT(("Transfer @ %p\n", this));
|
|
};
|
|
|
|
/** \brief Check if the Transfer is busy, i.e. may not be modified.
|
|
**/
|
|
bool busy() const {return ((m_state == State::pending) || (m_state == State::inProgress) || (m_state == State::error));}
|
|
|
|
/** \brief Check if the Transfer is done.
|
|
**/
|
|
bool done() const {return (m_state == State::eDone);}
|
|
|
|
// private:
|
|
volatile State m_state;
|
|
const uint8_t* m_pSource;
|
|
uint16_t m_transferCount;
|
|
volatile uint8_t* m_pDest;
|
|
uint8_t m_fill;
|
|
Transfer* m_pNext;
|
|
AbstractChipSelect* m_pSelect;
|
|
TransferType m_transferType;
|
|
|
|
uint8_t *m_pSourceIntermediate = nullptr;
|
|
volatile uint8_t *m_pDestIntermediate = nullptr;
|
|
volatile uint8_t *m_pDestOriginal = nullptr;
|
|
};
|
|
} // namespace DmaSpi
|
|
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
class AbstractDmaSpi
|
|
{
|
|
public:
|
|
using Transfer = DmaSpi::Transfer;
|
|
|
|
/** \brief arduino-style initialization.
|
|
*
|
|
* During initialization, two DMA channels are allocated. If that fails, this function returns false.
|
|
* If the channels could be allocated, those DMA channel fields that don't change during DMA SPI operation
|
|
* are initialized to the values they will have at runtime.
|
|
*
|
|
* \return true if initialization was successful; false otherwise.
|
|
* \see end()
|
|
**/
|
|
static bool begin()
|
|
{
|
|
if(init_count_ > 0)
|
|
{
|
|
return true; // this is not particularly bad, so we can return true
|
|
}
|
|
init_count_++;
|
|
DMASPI_PRINT(("DmaSpi::begin() : "));
|
|
// create DMA channels, might fail
|
|
if (!createDmaChannels())
|
|
{
|
|
DMASPI_PRINT(("could not create DMA channels\n"));
|
|
return false;
|
|
}
|
|
state_ = eStopped;
|
|
// tx: known destination (SPI), no interrupt, finish silently
|
|
begin_setup_txChannel();
|
|
if (txChannel_()->error())
|
|
{
|
|
destroyDmaChannels();
|
|
DMASPI_PRINT(("tx channel error\n"));
|
|
return false;
|
|
}
|
|
|
|
// rx: known source (SPI), interrupt on completion
|
|
begin_setup_rxChannel();
|
|
if (rxChannel_()->error())
|
|
{
|
|
destroyDmaChannels();
|
|
DMASPI_PRINT(("rx channel error\n"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void begin_setup_txChannel() {DMASPI_INSTANCE::begin_setup_txChannel_impl();}
|
|
static void begin_setup_rxChannel() {DMASPI_INSTANCE::begin_setup_rxChannel_impl();}
|
|
|
|
/** \brief Allow the DMA SPI to start handling Transfers. This must be called after begin().
|
|
* \see running()
|
|
* \see busy()
|
|
* \see stop()
|
|
* \see stopping()
|
|
* \see stopped()
|
|
**/
|
|
static void start()
|
|
{
|
|
DMASPI_PRINT(("DmaSpi::start() : state_ = "));
|
|
switch(state_)
|
|
{
|
|
case eStopped:
|
|
DMASPI_PRINT(("eStopped\n"));
|
|
state_ = eRunning;
|
|
beginPendingTransfer();
|
|
break;
|
|
|
|
case eRunning:
|
|
DMASPI_PRINT(("eRunning\n"));
|
|
break;
|
|
|
|
case eStopping:
|
|
DMASPI_PRINT(("eStopping\n"));
|
|
state_ = eRunning;
|
|
break;
|
|
|
|
default:
|
|
DMASPI_PRINT(("unknown\n"));
|
|
state_ = eError;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** \brief check if the DMA SPI is in running state.
|
|
* \return true if the DMA SPI is in running state, false otherwise.
|
|
* \see start()
|
|
* \see busy()
|
|
* \see stop()
|
|
* \see stopping()
|
|
* \see stopped()
|
|
**/
|
|
static bool running() {return state_ == eRunning;}
|
|
|
|
/** \brief register a Transfer to be handled by the DMA SPI.
|
|
* \return false if the Transfer had an invalid transfer count (zero or greater than 32767), true otherwise.
|
|
* \post the Transfer state is Transfer::State::pending, or Transfer::State::error if the transfer count was invalid.
|
|
**/
|
|
static bool registerTransfer(Transfer& transfer)
|
|
{
|
|
DMASPI_PRINT(("DmaSpi::registerTransfer(%p)\n", &transfer));
|
|
if ((transfer.busy())
|
|
|| (transfer.m_transferCount == 0) // no zero length transfers allowed
|
|
|| (transfer.m_transferCount >= 0x8000)) // max CITER/BITER count with ELINK = 0 is 0x7FFF, so reject
|
|
{
|
|
DMASPI_PRINT((" Transfer is busy or invalid, dropped\n"));
|
|
transfer.m_state = Transfer::State::error;
|
|
return false;
|
|
}
|
|
addTransferToQueue(transfer);
|
|
if ((state_ == eRunning) && (!busy()))
|
|
{
|
|
DMASPI_PRINT((" starting transfer\n"));
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
|
{
|
|
beginPendingTransfer();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/** \brief Check if the DMA SPI is busy, which means that it is currently handling a Transfer.
|
|
\return true if a Transfer is being handled.
|
|
* \see start()
|
|
* \see running()
|
|
* \see stop()
|
|
* \see stopping()
|
|
* \see stopped()
|
|
**/
|
|
static bool busy()
|
|
{
|
|
return (m_pCurrentTransfer != nullptr);
|
|
}
|
|
|
|
/** \brief Request the DMA SPI to stop handling Transfers.
|
|
*
|
|
* The stopping driver may finish a current Transfer, but it will then not start a new, pending one.
|
|
* \see start()
|
|
* \see running()
|
|
* \see busy()
|
|
* \see stopping()
|
|
* \see stopped()
|
|
**/
|
|
static void stop()
|
|
{
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
|
{
|
|
switch(state_)
|
|
{
|
|
case eStopped:
|
|
break;
|
|
case eRunning:
|
|
if (busy())
|
|
{
|
|
state_ = eStopping;
|
|
}
|
|
else
|
|
{
|
|
// this means that the DMA SPI simply has nothing to do
|
|
state_ = eStopped;
|
|
}
|
|
break;
|
|
case eStopping:
|
|
break;
|
|
default:
|
|
state_ = eError;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \brief See if the DMA SPI is currently switching from running to stopped state
|
|
* \return true if the DMA SPI is switching from running to stopped state
|
|
* \see start()
|
|
* \see running()
|
|
* \see busy()
|
|
* \see stop()
|
|
* \see stopped()
|
|
**/
|
|
static bool stopping() { return (state_ == eStopping); }
|
|
|
|
/** \brief See if the DMA SPI is stopped
|
|
* \return true if the DMA SPI is in stopped state, i.e. not handling pending Transfers
|
|
* \see start()
|
|
* \see running()
|
|
* \see busy()
|
|
* \see stop()
|
|
* \see stopping()
|
|
**/
|
|
static bool stopped() { return (state_ == eStopped); }
|
|
|
|
/** \brief Shut down the DMA SPI
|
|
*
|
|
* Deallocates DMA channels and sets the internal state to error (this might not be an intelligent name for that)
|
|
* \see begin()
|
|
**/
|
|
static void end()
|
|
{
|
|
if (init_count_ == 0)
|
|
{
|
|
state_ = eError;
|
|
return;
|
|
}
|
|
if (init_count_ == 1)
|
|
{
|
|
init_count_--;
|
|
destroyDmaChannels();
|
|
state_ = eError;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
init_count_--;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** \brief get the last value that was read from a slave, but discarded because the Transfer didn't specify a sink
|
|
**/
|
|
static uint8_t devNull()
|
|
{
|
|
return m_devNull;
|
|
}
|
|
|
|
protected:
|
|
enum EState
|
|
{
|
|
eStopped,
|
|
eRunning,
|
|
eStopping,
|
|
eError
|
|
};
|
|
|
|
static void addTransferToQueue(Transfer& transfer)
|
|
{
|
|
transfer.m_state = Transfer::State::pending;
|
|
transfer.m_pNext = nullptr;
|
|
DMASPI_PRINT((" DmaSpi::addTransferToQueue() : queueing transfer\n"));
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
|
{
|
|
if (m_pNextTransfer == nullptr)
|
|
{
|
|
m_pNextTransfer = &transfer;
|
|
}
|
|
else
|
|
{
|
|
m_pLastTransfer->m_pNext = &transfer;
|
|
}
|
|
m_pLastTransfer = &transfer;
|
|
}
|
|
}
|
|
|
|
static void post_finishCurrentTransfer() {DMASPI_INSTANCE::post_finishCurrentTransfer_impl();}
|
|
|
|
// finishCurrentTransfer is called from rxISR_()
|
|
static void finishCurrentTransfer()
|
|
{
|
|
DMASPI_PRINT((" inside finishCurrentTransfer()\n"));
|
|
if (m_pCurrentTransfer->m_pSelect != nullptr)
|
|
{
|
|
m_pCurrentTransfer->m_pSelect->deselect(m_pCurrentTransfer->m_transferType);
|
|
}
|
|
else
|
|
{
|
|
m_Spi.endTransaction();
|
|
}
|
|
m_pCurrentTransfer->m_state = Transfer::State::eDone;
|
|
DMASPI_PRINT((" finishCurrentTransfer() @ %p\n", m_pCurrentTransfer));
|
|
m_pCurrentTransfer = nullptr;
|
|
post_finishCurrentTransfer();
|
|
}
|
|
|
|
static bool createDmaChannels()
|
|
{
|
|
if (txChannel_() == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
if (rxChannel_() == nullptr)
|
|
{
|
|
delete txChannel_();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void destroyDmaChannels()
|
|
{
|
|
if (rxChannel_() != nullptr)
|
|
{
|
|
delete rxChannel_();
|
|
}
|
|
if (txChannel_() != nullptr)
|
|
{
|
|
delete txChannel_();
|
|
}
|
|
}
|
|
|
|
static DMAChannel* rxChannel_()
|
|
{
|
|
static DMAChannel* pChannel = new DMAChannel();
|
|
return pChannel;
|
|
}
|
|
|
|
static DMAChannel* txChannel_()
|
|
{
|
|
static DMAChannel* pChannel = new DMAChannel();
|
|
return pChannel;
|
|
}
|
|
|
|
static void rxIsr_()
|
|
{
|
|
DMASPI_PRINT(("DmaSpi::rxIsr_()\n"));
|
|
rxChannel_()->clearInterrupt();
|
|
// end current transfer: deselect and mark as done
|
|
|
|
// Check if intermediate buffer was used
|
|
if (m_pCurrentTransfer->m_pDestIntermediate) {
|
|
// copy when using an intermediate buffer
|
|
memcpy((void *)m_pCurrentTransfer->m_pDestOriginal, // DMA contents copied to original
|
|
(void *)m_pCurrentTransfer->m_pDest, // source is the actual DMA buffer
|
|
m_pCurrentTransfer->m_transferCount);
|
|
}
|
|
finishCurrentTransfer();
|
|
|
|
DMASPI_PRINT((" state = "));
|
|
switch(state_)
|
|
{
|
|
case eStopped: // this should not happen!
|
|
DMASPI_PRINT(("eStopped\n"));
|
|
state_ = eError;
|
|
break;
|
|
case eRunning:
|
|
DMASPI_PRINT(("eRunning\n"));
|
|
beginPendingTransfer();
|
|
break;
|
|
case eStopping:
|
|
DMASPI_PRINT(("eStopping\n"));
|
|
state_ = eStopped;
|
|
break;
|
|
case eError:
|
|
DMASPI_PRINT(("eError\n"));
|
|
break;
|
|
default:
|
|
DMASPI_PRINT(("eUnknown\n"));
|
|
state_ = eError;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void pre_cs() {DMASPI_INSTANCE::pre_cs_impl();}
|
|
static void post_cs() {DMASPI_INSTANCE::post_cs_impl();}
|
|
|
|
static void beginPendingTransfer()
|
|
{
|
|
if (m_pNextTransfer == nullptr)
|
|
{
|
|
DMASPI_PRINT(("DmaSpi::beginPendingTransfer: no pending transfer\n"));
|
|
return;
|
|
}
|
|
|
|
m_pCurrentTransfer = m_pNextTransfer;
|
|
DMASPI_PRINT(("DmaSpi::beginPendingTransfer: starting transfer @ %p\n", m_pCurrentTransfer));
|
|
m_pCurrentTransfer->m_state = Transfer::State::inProgress;
|
|
m_pNextTransfer = m_pNextTransfer->m_pNext;
|
|
if (m_pNextTransfer == nullptr)
|
|
{
|
|
DMASPI_PRINT((" this was the last in the queue\n"));
|
|
m_pLastTransfer = nullptr;
|
|
}
|
|
|
|
// configure Rx DMA
|
|
if (m_pCurrentTransfer->m_pDest != nullptr)
|
|
{
|
|
// real data sink
|
|
DMASPI_PRINT((" real sink\n"));
|
|
|
|
// Check for intermediate buffer
|
|
if (m_pCurrentTransfer->m_pDestIntermediate) {
|
|
// Modify the DMA so it will fill the intermediate buffer instead
|
|
// store the original buffer for memcpy in rx_isr()
|
|
m_pCurrentTransfer->m_pDestOriginal = m_pCurrentTransfer->m_pDest;
|
|
m_pCurrentTransfer->m_pDest = m_pCurrentTransfer->m_pDestIntermediate;
|
|
}
|
|
arm_dcache_flush_delete((void *)m_pCurrentTransfer->m_pDest, m_pCurrentTransfer->m_transferCount);
|
|
rxChannel_()->destinationBuffer(m_pCurrentTransfer->m_pDest,
|
|
m_pCurrentTransfer->m_transferCount);
|
|
}
|
|
else
|
|
{
|
|
// dummy data sink
|
|
DMASPI_PRINT((" dummy sink\n"));
|
|
rxChannel_()->destination(m_devNull);
|
|
rxChannel_()->transferCount(m_pCurrentTransfer->m_transferCount);
|
|
}
|
|
|
|
// configure Tx DMA
|
|
if (m_pCurrentTransfer->m_pSource != nullptr)
|
|
{
|
|
// real data source
|
|
if (m_pCurrentTransfer->m_pSourceIntermediate) {
|
|
// copy and use the intermediate buffer
|
|
memcpy((void*)m_pCurrentTransfer->m_pSourceIntermediate,
|
|
(void*)m_pCurrentTransfer->m_pSource,
|
|
m_pCurrentTransfer->m_transferCount
|
|
);
|
|
// DMA will now transfer from intermediate buffer
|
|
m_pCurrentTransfer->m_pSource = m_pCurrentTransfer->m_pSourceIntermediate;
|
|
}
|
|
DMASPI_PRINT((" real source\n"));
|
|
arm_dcache_flush_delete((void *)m_pCurrentTransfer->m_pSource, m_pCurrentTransfer->m_transferCount);
|
|
txChannel_()->sourceBuffer(m_pCurrentTransfer->m_pSource,
|
|
m_pCurrentTransfer->m_transferCount);
|
|
}
|
|
else
|
|
{
|
|
// dummy data source
|
|
DMASPI_PRINT((" dummy source\n"));
|
|
txChannel_()->source(m_pCurrentTransfer->m_fill);
|
|
txChannel_()->transferCount(m_pCurrentTransfer->m_transferCount);
|
|
}
|
|
|
|
DMASPI_PRINT(("calling pre_cs() "));
|
|
pre_cs();
|
|
|
|
// Select Chip
|
|
if (m_pCurrentTransfer->m_pSelect != nullptr)
|
|
{
|
|
m_pCurrentTransfer->m_pSelect->select(m_pCurrentTransfer->m_transferType);
|
|
}
|
|
else
|
|
{
|
|
m_Spi.beginTransaction(SPISettings());
|
|
}
|
|
|
|
DMASPI_PRINT(("calling post_cs() "));
|
|
post_cs();
|
|
}
|
|
|
|
static size_t init_count_;
|
|
static volatile EState state_;
|
|
static Transfer* volatile m_pCurrentTransfer;
|
|
static Transfer* volatile m_pNextTransfer;
|
|
static Transfer* volatile m_pLastTransfer;
|
|
static volatile uint8_t m_devNull;
|
|
//static SPICLASS& m_Spi;
|
|
};
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
size_t AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::init_count_ = 0;
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
volatile typename AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::EState AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::state_ = eError;
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
typename AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::Transfer* volatile AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::m_pNextTransfer = nullptr;
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
typename AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::Transfer* volatile AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::m_pCurrentTransfer = nullptr;
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
typename AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::Transfer* volatile AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::m_pLastTransfer = nullptr;
|
|
|
|
template<typename DMASPI_INSTANCE, typename SPICLASS, SPICLASS& m_Spi>
|
|
volatile uint8_t AbstractDmaSpi<DMASPI_INSTANCE, SPICLASS, m_Spi>::m_devNull = 0;
|
|
|
|
//void dump_dma(DMAChannel *dmabc)
|
|
//{
|
|
// Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD);
|
|
//
|
|
// Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR,
|
|
// dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR,
|
|
// dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
|
|
// Serial.flush();
|
|
//}
|
|
|
|
#if defined(__IMXRT1062__) // T4.0
|
|
|
|
class DmaSpi0 : public AbstractDmaSpi<DmaSpi0, SPIClass, SPI>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)IMXRT_LPSPI4_S.TDR);
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_TX);
|
|
//txChannel_()->triggerAtTransfersOf(*rxChannel_);
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)IMXRT_LPSPI4_S.RDR); // POPR is the receive fifo register for the SPI
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_RX); // The DMA RX id for MT66 is 14
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
if (LPSPI4_SR & 0x1800) {
|
|
DMASPI_PRINT(("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ERROR SR reg is %08X\n", LPSPI4_SR));
|
|
}
|
|
DMASPI_PRINT(("********************************************CHECK SR reg is %08X\n", LPSPI4_SR));
|
|
|
|
IMXRT_LPSPI4_S.TCR = (IMXRT_LPSPI4_S.TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(7);
|
|
IMXRT_LPSPI4_S.FCR = 0;
|
|
|
|
//IMXRT_LPSPI4_S.CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF;
|
|
IMXRT_LPSPI4_S.CR = LPSPI_CR_MEN; // I had to add the enable otherwise it wont' work
|
|
|
|
// Lets try to output the first byte to make sure that we are in 8 bit mode...
|
|
IMXRT_LPSPI4_S.DER = LPSPI_DER_TDDE | LPSPI_DER_RDDE; //enable DMA on both TX and RX
|
|
IMXRT_LPSPI4_S.SR = 0x3f00; // clear out all of the other status...
|
|
|
|
// if (m_pCurrentTransfer->m_pSource) {
|
|
// arm_dcache_flush((void *)m_pCurrentTransfer->m_pSource, m_pCurrentTransfer->m_transferCount);
|
|
// }
|
|
|
|
}
|
|
|
|
// static void pre_cs_impl()
|
|
// {
|
|
//
|
|
// //LPSPI4_PARAM = LPSPI4_PARAM;
|
|
// //LPSPI4_PARAM = 0x0404;
|
|
// //DMASPI_PRINT(("!!!!!!!!!!!!!!!!!!!!!PARAM reg is %08X\n", LPSPI4_PARAM));
|
|
// txChannel_()->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode...
|
|
// txChannel_()->TCD->SLAST = 0; // Finish with it pointing to next location
|
|
// rxChannel_()->TCD->ATTR_DST = 0; //Make sure set for 8 bit mode...
|
|
// rxChannel_()->TCD->DLASTSGA = 0;
|
|
//
|
|
// //DMASPI_PRINT(("STATUS SR reg is %08X\n", LPSPI4_SR));
|
|
// if (LPSPI4_SR & 0x1800) {
|
|
// DMASPI_PRINT(("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ERROR SR reg is %08X\n", LPSPI4_SR));
|
|
// }
|
|
// LPSPI4_SR = 0x3f00; // clear various error and status flags
|
|
// DMASPI_PRINT(("********************************************CHECK SR reg is %08X\n", LPSPI4_SR));
|
|
//
|
|
// LPSPI4_TCR = (LPSPI4_TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(7); // Set the FRAMESZ to 7 for 8-bit frame size
|
|
// LPSPI4_FCR = 0; // set watermarks to zero, this ensures ready flag is set whenever fifo is not empty
|
|
//
|
|
// LPSPI4_CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF; //enable module and reset both FIFOs
|
|
// LPSPI4_DER = LPSPI_DER_TDDE | LPSPI_DER_RDDE; // enable DMA on both TX and RX
|
|
// }
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
DMASPI_PRINT(("Done post_cs_impl()\n"));
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
IMXRT_LPSPI4_S.FCR = LPSPI_FCR_TXWATER(15); // _spi_fcr_save; // restore the FSR status...
|
|
IMXRT_LPSPI4_S.DER = 0; // DMA no longer doing TX (or RX)
|
|
|
|
IMXRT_LPSPI4_S.CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF; // actually clear both...
|
|
IMXRT_LPSPI4_S.SR = 0x3f00; // clear out all of the other status...
|
|
|
|
// if (m_pCurrentTransfer->m_pDest) {
|
|
// arm_dcache_delete((void *)m_pCurrentTransfer->m_pDest, m_pCurrentTransfer->m_transferCount);
|
|
// }
|
|
}
|
|
|
|
// static void post_finishCurrentTransfer_impl()
|
|
// {
|
|
// //LPSPI4_FCR = LPSPI_FCR_TXWATER(15); // restore FSR status
|
|
// LPSPI4_DER = 0; // DMA no longer doing TX or RX
|
|
// LPSPI4_CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF; //enable module and reset both FIFOs
|
|
// LPSPI4_SR = 0x3f00; // clear out all the other statuses
|
|
// }
|
|
|
|
private:
|
|
};
|
|
|
|
extern DmaSpi0 DMASPI0;
|
|
|
|
#elif defined(KINETISK)
|
|
|
|
class DmaSpi0 : public AbstractDmaSpi<DmaSpi0, SPIClass, SPI>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)SPI0_PUSHR); // PUSHR is the transmit fifo register for the SPI
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX); // The DMA TX id for MT66 is 15
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)SPI0_POPR); // POPR is the receive fifo register for the SPI
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX); // The DMA RX id for MT66 is 14
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
SPI0_SR = 0xFF0F0000; // Clear various flags including Transfer complete, TXRX Status, End of Queue, Transmit FIFO underflow, Transmit FIFO Fill, Rx FIFO overflow, Rx fifo drain
|
|
|
|
// Request Select Enable Register
|
|
// RFDF_RE Rx fifo drain request enable, enables the RFDF flag in SPI0_SR
|
|
// RFDF_DIRS Rx fifo drain selects DMA request instead of interrupt request
|
|
// TFFF_RE Transmit Fifo fill request enable
|
|
// TFFF_DIRS Transmit fifo fill selct DMA instead of interrupt
|
|
SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
|
|
}
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
SPI0_RSER = 0; //DSPI DMA/Interrupt Request Select and Enable Register
|
|
SPI0_SR = 0xFF0F0000; // DSPI status register clear flags, same as above
|
|
}
|
|
|
|
private:
|
|
};
|
|
|
|
extern DmaSpi0 DMASPI0;
|
|
|
|
|
|
#if defined(__MK66FX1M0__)
|
|
|
|
class DmaSpi1 : public AbstractDmaSpi<DmaSpi1, SPIClass, SPI1>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)SPI1_PUSHR);
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI1_TX);
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)SPI1_POPR);
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI1_RX);
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
SPI1_SR = 0xFF0F0000;
|
|
SPI1_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
|
|
}
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
SPI1_RSER = 0;
|
|
SPI1_SR = 0xFF0F0000;
|
|
}
|
|
|
|
private:
|
|
};
|
|
|
|
/*
|
|
class DmaSpi2 : public AbstractDmaSpi<DmaSpi2, SPIClass, SPI2>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)SPI2_PUSHR);
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI2_TX);
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)SPI2_POPR);
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI2_RX);
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
SPI2_SR = 0xFF0F0000;
|
|
SPI2_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
|
|
}
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
SPI2_RSER = 0;
|
|
SPI2_SR = 0xFF0F0000;
|
|
}
|
|
|
|
private:
|
|
};
|
|
*/
|
|
|
|
extern DmaSpi1 DMASPI1;
|
|
//extern DmaSpi2 DMASPI2;
|
|
#endif // defined(__MK66FX1M0__)
|
|
|
|
#elif defined(KINETISL)
|
|
class DmaSpi0 : public AbstractDmaSpi<DmaSpi0, SPIClass, SPI>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)SPI0_DL);
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)SPI0_DL);
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_RX);
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
// disable SPI and enable SPI DMA requests
|
|
SPI0_C1 &= ~(SPI_C1_SPE);
|
|
SPI0_C2 |= SPI_C2_TXDMAE | SPI_C2_RXDMAE;
|
|
}
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
SPI0_C2 = 0;
|
|
txChannel_()->clearComplete();
|
|
rxChannel_()->clearComplete();
|
|
}
|
|
|
|
private:
|
|
};
|
|
|
|
class DmaSpi1 : public AbstractDmaSpi<DmaSpi1, SPIClass, SPI1>
|
|
{
|
|
public:
|
|
static void begin_setup_txChannel_impl()
|
|
{
|
|
txChannel_()->disable();
|
|
txChannel_()->destination((volatile uint8_t&)SPI1_DL);
|
|
txChannel_()->disableOnCompletion();
|
|
txChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI1_TX);
|
|
}
|
|
|
|
static void begin_setup_rxChannel_impl()
|
|
{
|
|
rxChannel_()->disable();
|
|
rxChannel_()->source((volatile uint8_t&)SPI1_DL);
|
|
rxChannel_()->disableOnCompletion();
|
|
rxChannel_()->triggerAtHardwareEvent(DMAMUX_SOURCE_SPI1_RX);
|
|
rxChannel_()->attachInterrupt(rxIsr_);
|
|
rxChannel_()->interruptAtCompletion();
|
|
}
|
|
|
|
static void pre_cs_impl()
|
|
{
|
|
// disable SPI and enable SPI DMA requests
|
|
SPI1_C1 &= ~(SPI_C1_SPE);
|
|
SPI1_C2 |= SPI_C2_TXDMAE | SPI_C2_RXDMAE;
|
|
}
|
|
|
|
// static void dumpCFG(const char *sz, uint32_t* p)
|
|
// {
|
|
// DMASPI_PRINT(("%s: %x %x %x %x \n", sz, p[0], p[1], p[2], p[3]));
|
|
// }
|
|
|
|
static void post_cs_impl()
|
|
{
|
|
DMASPI_PRINT(("post_cs S C1 C2: %x %x %x\n", SPI1_S, SPI1_C1, SPI1_C2));
|
|
// dumpCFG("RX", (uint32_t*)(void*)rxChannel_()->CFG);
|
|
// dumpCFG("TX", (uint32_t*)(void*)txChannel_()->CFG);
|
|
rxChannel_()->enable();
|
|
txChannel_()->enable();
|
|
}
|
|
|
|
static void post_finishCurrentTransfer_impl()
|
|
{
|
|
SPI1_C2 = 0;
|
|
txChannel_()->clearComplete();
|
|
rxChannel_()->clearComplete();
|
|
}
|
|
private:
|
|
};
|
|
|
|
extern DmaSpi0 DMASPI0;
|
|
extern DmaSpi1 DMASPI1;
|
|
|
|
#else
|
|
|
|
#error Unknown chip
|
|
|
|
#endif // KINETISK else KINETISL
|
|
|
|
class DmaSpiGeneric
|
|
{
|
|
public:
|
|
using Transfer = DmaSpi::Transfer;
|
|
|
|
DmaSpiGeneric() {
|
|
m_spiDma0 = &DMASPI0;
|
|
#if defined(__MK66FX1M0__)
|
|
m_spiDma1 = &DMASPI1;
|
|
#endif
|
|
}
|
|
DmaSpiGeneric(int spiId) : DmaSpiGeneric() {
|
|
m_spiSelect = spiId;
|
|
}
|
|
|
|
bool begin () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->begin();
|
|
default :
|
|
return m_spiDma0->begin();
|
|
}
|
|
}
|
|
|
|
void start () {
|
|
switch(m_spiSelect) {
|
|
case 1 : m_spiDma1->start(); return;
|
|
default :
|
|
m_spiDma0->start(); return;
|
|
}
|
|
}
|
|
|
|
bool running () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->running();
|
|
default :
|
|
return m_spiDma0->running();
|
|
}
|
|
}
|
|
|
|
bool registerTransfer (Transfer& transfer) {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->registerTransfer(transfer);
|
|
default :
|
|
return m_spiDma0->registerTransfer(transfer);
|
|
}
|
|
}
|
|
|
|
|
|
bool busy () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->busy();
|
|
default :
|
|
return m_spiDma0->busy();
|
|
}
|
|
}
|
|
|
|
void stop () {
|
|
switch(m_spiSelect) {
|
|
case 1 : m_spiDma1->stop(); return;
|
|
default :
|
|
m_spiDma0->stop(); return;
|
|
}
|
|
}
|
|
|
|
bool stopping () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->stopping();
|
|
default :
|
|
return m_spiDma0->stopping();
|
|
}
|
|
}
|
|
|
|
bool stopped () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->stopped();
|
|
default :
|
|
return m_spiDma0->stopped();
|
|
}
|
|
}
|
|
|
|
void end () {
|
|
switch(m_spiSelect) {
|
|
case 1 : m_spiDma1->end(); return;
|
|
default :
|
|
m_spiDma0->end(); return;
|
|
}
|
|
}
|
|
|
|
uint8_t devNull () {
|
|
switch(m_spiSelect) {
|
|
case 1 : return m_spiDma1->devNull();
|
|
default :
|
|
return m_spiDma0->devNull();
|
|
}
|
|
}
|
|
|
|
private:
|
|
int m_spiSelect = 0;
|
|
DmaSpi0 *m_spiDma0 = nullptr;
|
|
#if defined(__MK66FX1M0__)
|
|
DmaSpi1 *m_spiDma1 = nullptr;
|
|
#else
|
|
// just make it Spi0 so it compiles atleast
|
|
DmaSpi0 *m_spiDma1 = nullptr;
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
#endif // DMASPI_H
|
|
|