cancel
Showing results for 
Search instead for 
Did you mean: 

Delays between SPI transfers on STM32F411 using DMA

cpalmer54
Associate II

Hello,

I am seeking help trying to understand and hopefully fix. I am writing data to a 32 channel SPI DAC. I am writing 3 bytes to each channel. To write to all channels and update simultaneously i trigger a LDA line after all 32 channels have been written to. what I see is large delays between 3 byte transmissions. I am using DMA and interupts.

The SPI port is set at 24MHZ and micro at 75MHZ using the PLL HSI. The 3 bytes transfer fine at 24Mhz. This takes approx 950nS. I then see a 2.5uS delay before the next 3 bytes are sent. This kills the overall time taken to something that breaches my spec. Full 32 channels taking approx. 105mS. I want it closer to 32ms. I'm sure I am overlooking something, but it has currently stumped me and my project is starting to slip. Any help much appreciated.

/**************** SPI DMA setup *******************/

void InitSPI3 ( void )
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStruct;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

// configure pins used by SPI - PC10 = SCK, PC11 = MISO, PC12 = MOSI
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //GPIO_PuPd_NOPULL; //;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3);

SPI_I2S_DeInit(SPI3);
SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx; //SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //SPI_DataSize_16b
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // SPI_NSS_Hard
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // falling edge clock
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // default SPI_MODE_0
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // SPI at max 25Mbps
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_CRCPolynomial = 0; // NO CRC used
SPI_Init(SPI3, &SPI_InitStruct); // Initialise
SPI_Cmd(SPI3, ENABLE);

DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

DMA_DeInit(DMA1_Stream7);
DMA_StructInit(&DMA_InitStructure);

// Configure DMA1 Channel0 - TX Stream 7
DMA_DeInit(DMA1_Stream7);
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI3->DR;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&SPITxBuffer[0]; //array data transmitted
DMA_InitStructure.DMA_BufferSize = sizeof(SPITxBuffer); //Buffer size
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
// DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
// DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_Init(DMA1_Stream7, &DMA_InitStructure); //Initialise the DMA

DMA_ITConfig(DMA1_Stream7, DMA_IT_TC, ENABLE); // enable transfer complete interrupt

// SPI interrupt enable - dont really need if we are just transmitting in a loop
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //SPI3_IRQn
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

DMA_ClearFlag(DMA1_Stream7, DMA_IT_TCIF7);
DMA_ClearITPendingBit(DMA1_Stream7, DMA_IT_TCIF7);
DMA_Cmd(DMA1_Stream7, ENABLE);
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);

}

/***************** SPI DMA ISR *****************/

void SPI3_DMA1_ISR()
{
// Test on DMA Stream Transfer Complete interrupt
    if (DMA_GetITStatus(DMA1_Stream7, DMA_IT_TCIF7))
    {
        // Clear DMA Stream Transfer Complete interrupt pending bit
       DMA_ClearITPendingBit(DMA1_Stream7, DMA_IT_TCIF7);
    }
}

/********************** code that sets up the bytes and performs the 3 byte DMA transfer ************/

error_t WriteAD5383( uint8_t chanid, uint16_t value )
{
    error_t dacerr = ERR_NONE;

    /* Check channel is valid */
    if( chanid > 32)
    {
        return ERR_INVALID_CHANNEL;
    }

    /*
    * Form data for the AD5383 device
    * 23 23 22 21 18 17 16 15 14 ..... 5 4 3 2 1 0
    * A/B R/W 0 A4.. A0 REG1 REG0 D11 ............D1 D0 X X
    *
    * X = don't care
    * A/B = Register select
    * Ax = Input channel address
    * Dx = data bits
    */
    // add reg bits
    value = value << 2;
    /* channel in lower nibble of byte 0 */
    SPITxBuffer[0] = (uint8_t)chanid;

    /* DAC value bits 11:6 in byte 1 and Add Reg 0/1 bits */
    SPITxBuffer[1] = (uint8_t)(value >> ‌‌ | 0xC0;

    /* DAC value bits 5:0 in byte 2 */
    SPITxBuffer[2] = (uint8_t)(value | 0x00);

    // Wait if TXE indicates busy
    while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE) == RESET); // wait for empty buffer TODO timeout

    /* Assert chip select */
    SetDIO( DIOID_DAC_CS, DIOSTATE_ASSERT );

    DMA_Cmd(DMA1_Stream7, DISABLE);

    DMA1_Stream7->M0AR = (uint32_t)SPITxBuffer;
    DMA1_Stream7->NDTR = sizeof(SPITxBuffer);
    DMA_Cmd(DMA1_Stream7, ENABLE);

    // Wait until data is sent
    // while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_BSY) == SET);
    while(!SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE));

    // Deassert chip select
    SetDIO( DIOID_DAC_CS, DIOSTATE_DEASSERT );

    return dacerr;
}


/*********** code that performs the 3 bytes writes, looped for the 32 channels ***********/

void InitDACs( void )
{
    SetDIO (DIOID_DAC_LDAC, DIOSTATE_DEASSERT);
    for (uint8_t i = 0; i <= 32; i++)
    {
        WriteAD5383(i,0x07FF);
    }
    // Set LDAC to update all DAC channels simultaneously
    SetDIO (DIOID_DAC_LDAC, DIOSTATE_ASSERT);
}


/******************* clock setup ***************/

void InitRCC ( void )
{
    RCC_DeInit();FLASH->ACR |= FLASH_ACR_LATENCY_4WS; // three wait states
    FLASH->ACR |= FLASH_ACR_PRFTEN; // prefetch enable
    FLASH->ACR |= FLASH_ACR_ICEN; // instruction cache enable
    FLASH->ACR |= FLASH_ACR_DCEN; // data cache enable

    FLASH_OB_Unlock();

    RCC_HSEConfig(RCC_HSE_OFF);
    RCC_HSICmd(ENABLE);
    PWR->CR=0x11; // Set power scale register on.

    while( RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);

    // PLL_M = 16, PLL_N 400, PLL_P = 4, PLL_Q = 9
    RCC_PLLConfig(RCC_PLLSource_HSI, 16, 400, 4, 9); // mclk 1mhz
    RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK ); // Use PLL as system clock
    RCC_PLLCmd( ENABLE ); // Enable PLL

   PWR->CR=0x11; // Set power scale register on.

   while( RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET ) continue; // Wait till PLL is ready

    while( RCC_GetSYSCLKSource() != 0x08 ) continue; // Wait till PLL is stable

    RCC_HCLKConfig(RCC_SYSCLK_Div1); // HCLK = SYSCLK = 100MHZ
    RCC_PCLK1Config(RCC_HCLK_Div2); // PCLK1 = HCLK/4 APB1 - 50Mhz
    RCC_PCLK2Config(RCC_HCLK_Div1); // PCLK2 = HCLK/4 APB2 - 100Mhz
}
10 REPLIES 10
gbm
Lead III

For SPI, DMA becomes reasonable at transfer length of 3 frames (assuming you don't loose time on HAL calls). Use polling for 1..2 frames, don't use interrupts.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice