Hi all,
I'm been struggling with SPI over DMA.
In my hardware I have EUSCI port B0 connected as my SPI port, and I'd like to use DMA channel 2 for SPI Tx and DMA channel 3 for SPI Rx.
(I can't use Ch0 for Tx and Ch1 for Rx in my final code because I'm using EUSCI A0 for a UART and A0 can only be mapped to CH0/1)
Now, in the following code, everything works fine if I map B0 to Ch0/1, but it I map to any other channels the code just doesn't work.
If I don't put the SPI Tx onto CH0, I never see any data come out the SPI port (and consequently there is no data recieved either)
If I don't put the SPI Rx onto CH1, the DMA Rx interrupt never fires.
Why can't I use different DMA channels for this port? What do I need to do differently?
As an additional question, why does every DMA example I come across use the line
#pragma DATA_ALIGN(controlTable, 256)
but the MSP432 Peripheral Driver Library users guide says that controlTable needs to be aligned on a 1024 byte boundary??
// ---------------------------------------------------------------------------
// Don't forget to insert dma_[1,2,3]_interrupt() into your interrupt mapping
// (e.g. in your startup_mspXXX.c file)
// ---------------------------------------------------------------------------
// INCLUDES
// ---------------------------------------------------------------------------
#include "driverlib.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// ---------------------------------------------------------------------------
// DEFINES
// ---------------------------------------------------------------------------
/* DMA Control Table */
#ifdef ewarm
#pragma data_alignment=256
#else
// QUESTION -> MSP432 Peripheral Driver Library User's Guide (4Nov2015)
// Page 101 says controlTable should be aligned on 1024 byte
// boundary, but all code examples I can find set it to 256.
#pragma DATA_ALIGN(controlTable, 256)
#endif
// QUESTION -> If using TX and RX, does the RX have to be on TX channel + 1???
// QUESTION -> Why does TX never start if not using DMA CH 0 ??
#define DMA_TX_MAPPING DMA_CH0_EUSCIB0TX0
//#define DMA_TX_MAPPING DMA_CH2_EUSCIB0TX1
//#define DMA_TX_MAPPING DMA_CH4_EUSCIB0TX2
//#define DMA_TX_MAPPING DMA_CH6_EUSCIB0TX3
#define DMA_TX_CHANNEL (DMA_TX_MAPPING & 0x0f) // This matches what is done in many places in mspware dma.c. LSNibble of channel mapping macro is the channel number
// QUESTION -> Why does RX never complete (interrupt doesn't fire) if not using DMA CH1 ???
#define DMA_RX_MAPPING DMA_CH1_EUSCIB0RX0
//#define DMA_RX_MAPPING DMA_CH3_EUSCIB0RX1
//#define DMA_RX_MAPPING DMA_CH5_EUSCIB0RX2
//#define DMA_RX_MAPPING DMA_CH6_EUSCIB0TX3
#define DMA_RX_CHANNEL (DMA_RX_MAPPING & 0x0f)
// Interrupt assignment seems to make no difference to above problems
#define DMA_INT_ASSIGNMENT DMA_INT1 // DMA_INTx == INT_DMA_INTx
#define SPI_PORT EUSCI_B0_BASE // One of EUSCI_xx_BASE
#define SPI_nCS_PORT GPIO_PORT_P4
#define SPI_nCS_PIN GPIO_PIN6
// ---------------------------------------------------------------------------
// TYPEDEFS
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// GLOBALS
// ---------------------------------------------------------------------------
uint8_t controlTable[256];
/* SPI Master Configuration Parameter */
const eUSCI_SPI_MasterConfig spiMasterConfig =
{
EUSCI_SPI_CLOCKSOURCE_SMCLK,
12000000,
2000000,
EUSCI_A_SPI_MSB_FIRST,
EUSCI_A_SPI_PHASE_DATA_CHANGED_ONFIRST_CAPTURED_ON_NEXT,
EUSCI_A_SPI_CLOCKPOLARITY_INACTIVITY_HIGH,
EUSCI_A_SPI_3PIN
};
// ---------------------------------------------------------------------------
// PROTOTYPES
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// IMPLEMENTATION
// ---------------------------------------------------------------------------
int main(void)
{
uint8_t tx_data_array[6] = {3,4,5,6,7,8};
uint8_t rx_data_array[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
/* Halting Watchdog */
MAP_WDT_A_holdTimer();
MAP_Interrupt_enableMaster();
// Setup up clocks
MAP_CS_setReferenceOscillatorFrequency(CS_REFO_32KHZ);
MAP_CS_initClockSignal(CS_MCLK, CS_MODOSC_SELECT, CS_CLOCK_DIVIDER_1); //24MHz
MAP_CS_initClockSignal(CS_HSMCLK, CS_MODOSC_SELECT, CS_CLOCK_DIVIDER_1); //24MHz
MAP_CS_initClockSignal(CS_SMCLK, CS_MODOSC_SELECT, CS_CLOCK_DIVIDER_2); //12MHz
MAP_CS_initClockSignal(CS_ACLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1); //32kHz
MAP_CS_initClockSignal(CS_BCLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1); //32kHz
// Setup pins for SPI port B0
MAP_GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1,
GPIO_PIN5 | GPIO_PIN6 | GPIO_PIN7,
GPIO_PRIMARY_MODULE_FUNCTION);
// Setup my nCS pin
MAP_GPIO_setOutputHighOnPin(SPI_nCS_PORT, SPI_nCS_PIN);
MAP_GPIO_setDriveStrengthLow(SPI_nCS_PORT, SPI_nCS_PIN);
MAP_GPIO_setAsOutputPin(SPI_nCS_PORT, SPI_nCS_PIN);
// Setup SPI
MAP_SPI_initMaster(SPI_PORT, &spiMasterConfig);
MAP_SPI_enableModule(SPI_PORT);
// Setup DMA
MAP_DMA_enableModule();
MAP_DMA_setControlBase(controlTable);
MAP_DMA_assignChannel(DMA_TX_MAPPING);
MAP_DMA_assignChannel(DMA_RX_MAPPING);
MAP_DMA_disableChannelAttribute(DMA_TX_CHANNEL,
UDMA_ATTR_ALTSELECT |
UDMA_ATTR_USEBURST |
UDMA_ATTR_HIGH_PRIORITY |
UDMA_ATTR_REQMASK);
MAP_DMA_disableChannelAttribute(DMA_RX_CHANNEL,
UDMA_ATTR_ALTSELECT |
UDMA_ATTR_USEBURST |
UDMA_ATTR_HIGH_PRIORITY |
UDMA_ATTR_REQMASK);
MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_TX_CHANNEL,
UDMA_SIZE_8 |
UDMA_SRC_INC_8 |
UDMA_DST_INC_NONE |
UDMA_ARB_1);
MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_RX_CHANNEL,
UDMA_SIZE_8 |
UDMA_SRC_INC_NONE |
UDMA_DST_INC_8 |
UDMA_ARB_1);
MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_TX_CHANNEL,
UDMA_MODE_BASIC,
tx_data_array,
(void*) MAP_SPI_getTransmitBufferAddressForDMA(SPI_PORT),
6);
MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_RX_CHANNEL,
UDMA_MODE_BASIC,
(void*) MAP_SPI_getReceiveBufferAddressForDMA(SPI_PORT),
rx_data_array,
6);
// We want to get an interrupt when RX is done, which is when the SPI
// operation is fully complete
if (DMA_INT_ASSIGNMENT != DMA_INT0) // DMA channels are mapped to Int0 by default
{
MAP_DMA_assignInterrupt(DMA_INT_ASSIGNMENT, DMA_RX_CHANNEL);
}
// DMA is ready to go
// Check SPI is ready to go
// SPI TX DMA triggers when it sees the transmit interrupt for the SPI Port high
// (i.e. ready to transmit)
while (!(MAP_SPI_getInterruptStatus(SPI_PORT, EUSCI_SPI_TRANSMIT_INTERRUPT)));
// Pull nCS low.
MAP_GPIO_setOutputLowOnPin(SPI_nCS_PORT, SPI_nCS_PIN);
// Enable the RX channel first so that is ready to receive chars once we start TXing
MAP_DMA_enableChannel(DMA_RX_CHANNEL); // This also does a DMA_enableInterrupt()
MAP_DMA_enableChannel(DMA_TX_CHANNEL); // This also does a DMA_enableInterrupt()
MAP_Interrupt_enableInterrupt(DMA_INT_ASSIGNMENT);
MAP_PCM_gotoLPM0InterruptSafe();
while(1);
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void dma_main_interrupt(uint32_t interrupt_u32)
{
// No flags to clear!
uint32_t dma_mode_u32;
dma_mode_u32 = MAP_DMA_getChannelMode(DMA_TX_CHANNEL | UDMA_PRI_SELECT);
if (dma_mode_u32 == UDMA_MODE_STOP)
{
MAP_DMA_disableChannel(DMA_TX_CHANNEL);
MAP_DMA_disableChannel(DMA_RX_CHANNEL);
MAP_DMA_disableInterrupt(interrupt_u32);
GPIO_setOutputHighOnPin(SPI_nCS_PORT, SPI_nCS_PIN);
}
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void dma_0_interrupt(void)
{
// We are going to get both Rx and Tx interrupts because
// all DMA channels are mapped to DMA0 by default.
// We really only want to handle RX interrupts
uint32_t status_u32;
uint8_t dma_ch_u8;
uint32_t dma_ch_mask_u32 = 1;
status_u32 = MAP_DMA_getInterruptStatus();
for (dma_ch_u8 = DMA_CHANNEL_0; dma_ch_u8 <= DMA_CHANNEL_7; dma_ch_u8++)
{
// status_u32 is a bit mask
if (status_u32 & dma_ch_mask_u32)
{
// clearInterruptFlag is a single channel number
MAP_DMA_clearInterruptFlag(dma_ch_u8);
if (dma_ch_u8 == DMA_RX_CHANNEL) dma_main_interrupt(INT_DMA_INT0);
}
dma_ch_mask_u32 = dma_ch_mask_u32 << 1;
}
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void dma_1_interrupt(void)
{
// No flags to clear!
dma_main_interrupt(INT_DMA_INT1);
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void dma_2_interrupt(void)
{
// No flags to clear!
dma_main_interrupt(INT_DMA_INT2);
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
void dma_3_interrupt(void)
{
// No flags to clear!
dma_main_interrupt(INT_DMA_INT3);
}