2025-01-14 02:16 AM - last edited on 2025-01-14 02:44 AM by waclawek.jan
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
}
2025-01-23 05:05 AM
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.