Electronic – Full duplex slave SPI, DMA and interrupts on STM32F103

stm32

I am looking for a bit of help with SPI and DMA on an STM32F103C8 board.

I have it working as an SPI slave using interrupts and am have trouble getting DMA to work. I'm aiming to get full duplex 16 bit transactions, with a DMA interrupt being generated on the reception of data. My thoughts behind this are if I populate the tx buffer and enable DMA for both tx and rx, then when the slave select is pulled low (configured as hardware slave) rx data will be received as the master clocks out tx data. When it is done, the rx interrupt will occur, I can check flags etc for tx in the DMA interrupt and re enable DMA to repeat.

I've managed to get DMA and SPI working to receive data, but I just can't get it to transmit anything. I'm watching on a logic analyser but all is quiet on the SDO front. I've tried crying, swearing and beer, but none seem to work. I am fairly convinced my problem is in my DMA setup. Can anyone please take a look at my code to see if I'm on the right track?

Setup and enable:

// Setup Code
GPIO_InitTypeDef    GPIO_InitStructure;
SPI_InitTypeDef     SPI_InitStructure;
NVIC_InitTypeDef    NVIC_InitStructure;
DMA_InitTypeDef     DMA_InitStructure;

volatile uint16_t   dummyRX;
volatile uint16_t   dummyTX;

// Host SPI Interface
RCC_APB2PeriphClockCmd(SPI_GPIO_CLK | SPI_CLK, ENABLE);         // Enable the clock for SPI1

// Configure GPIO for SPI slave: SCK, MOSI, SS as inputs
GPIO_InitStructure.GPIO_Pin = MOSI_PIN | CS_PIN | SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;           // Configure SCK and MOSI pins as Input Floating 
GPIO_Init(SCK_PORT, &GPIO_InitStructure);

// Configure GPIO for SPI slave: MISO as AF output
GPIO_InitStructure.GPIO_Pin = MISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MISO_PORT, &GPIO_InitStructure);



SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;          // CHECK! pg682 - Should be full duplex as we need to xmit all F's for 485 drivers when recieving 
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                     // SCK idle high
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                    // second transition -> SCK Idle high => capture on rising edge of clock
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
//SPI_BaudRatePrescaler
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
//SPI_CRCPolynomial                  
SPI_Init(SPI1, &SPI_InitStructure);




//Enable DMA1 channel IRQ Channel 
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);


RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


DMA_DeInit(DMA1_Channel2);
DMA_DeInit(DMA1_Channel3); 
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI_DR_Base;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;


DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&dummyRX;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);


DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&dummyTX;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);



dummyTX = 0xAFFA;       // Some data to send
dummyRX = 0x0000;



// Start the devices
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);                 // Enable DMA1 Channel Transfer Complete interrupt

SPI_Cmd(SPI1, ENABLE);                                          // Enable SPI device
SPI_CalculateCRC(SPI1, DISABLE);

SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);

DMA_Cmd(DMA1_Channel3, ENABLE);
DMA_Cmd(DMA1_Channel2, ENABLE);                                 // Enable DMA channels

DMA rx (channel 2) interrupt

    //Test on DMA1 Channel1 Transfer Complete interrupt
if(DMA_GetITStatus(DMA1_IT_TC2)) {
    //Clear DMA1 Channel1 Half Transfer, Transfer Complete and Global interrupt pending bits
    DMA_ClearITPendingBit(DMA1_IT_GL2);

    LED_1_PORT->ODR ^= LED_1_PIN;                       // Toggle test LED


    while (DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET) {}
    // wait for tx to complete - page 692
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) {}
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) {}

    DMA_ClearFlag(DMA1_FLAG_GL3);                       // Clear the global flag

    spi_buffer_rx = dummyRX;

    // Disable DMA
    DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, DISABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
    DMA_Cmd(DMA1_Channel2, DISABLE);
    DMA_Cmd(DMA1_Channel3, DISABLE);

    while (DMA1_Channel2->CCR & DMA_CCR2_EN);           // wait until DMA is actually off
    while (DMA1_Channel3->CCR & DMA_CCR3_EN);   


    DMA_SetCurrDataCounter(DMA1_Channel2, 1);           // Set the number of transfers
    DMA_SetCurrDataCounter(DMA1_Channel3, 1);

    DMA_ClearITPendingBit(DMA1_IT_GL2);     // clear again
    DMA_ClearFlag(DMA1_FLAG_GL3);           // Clear the global flag

    // Start DMA controller again
    DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Cmd(DMA1_Channel2, ENABLE);
    DMA_Cmd(DMA1_Channel3, ENABLE);

    }

Best Answer

Below a code that works for me. DMA channel assignment is critical. DMA1 channel 4 for SPI2 RX request and DMA1 channel 5 for SPI2 TX request. See here, page 147:

http://www.st.com/web/en/resource/technical/document/reference_manual/CD00246267.pdf

uint16_t SPIReceivedValue[2];
uint16_t SPITransmittedValue[2] = {0xFF00,0x00FF};

void SPI_Slave_and_DMA_Configuration(void)
{
    SPI_InitTypeDef SPI_InitStructure; //Variable used to setup the SPI
    DMA_InitTypeDef DMA_InitStructure; //Variable used to setup the DMA


    //--Enable the SPI2 periph
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    // Reset SPI Interface
    SPI_I2S_DeInit(SPI2);

    //== SPI2 configuration
    SPI_StructInit(&SPI_InitStructure);
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI2, &SPI_InitStructure);


    //--Enable DMA1 clock--
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    //==Configure DMA1 - Channel4== (SPI -> memory)
    DMA_DeInit(DMA1_Channel4); //Set DMA registers to default values
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR; //Address of peripheral the DMA must map to
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&SPIReceivedValue[0]; //Variable to which received data will be stored
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 2; //Buffer size
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure); //Initialise the DMA
    DMA_Cmd(DMA1_Channel4, ENABLE); //Enable the DMA1 - Channel4         

    //==Configure DMA1 - Channel5== (memory -> SPI)
    DMA_DeInit(DMA1_Channel5); //Set DMA registers to default values
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR; //Address of peripheral the DMA must map to
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&SPITransmittedValue[0]; //Variable from which data will be transmitted
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 2; //Buffer size
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5, &DMA_InitStructure); //Initialise the DMA
    DMA_Cmd(DMA1_Channel5, ENABLE); //Enable the DMA1 - Channel5         

    // Enable SPI2
    SPI_Cmd(SPI2, ENABLE);

    // Enable the SPI2 RX & TX DMA requests
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
}