1
\$\begingroup\$

So for my project I use STM32L4R5 (custom PCB) and need to connect parallel 8 wires to control addresses in a sensor. I am toggling 8 GPIO pins in parallel providing the addresses, but speed is crazy low (I would need 10~20MHz). I checked several other similar problems in different sites and optimized as much as I can. I guess it may be very simple error but what I have at the moment is as follows:

MXCube settings: enter image description here

basically, I have 48MHz crystal, I use PLL to go up to 100MHz for system clock, 80MHz for ADC (different topic...). I actually touched the code a few times testing between 48MHz and less so I paste also the final clock settings in code:

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 12;
  RCC_OscInitStruct.PLL.PLLN = 50;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;//RCC_SYSCLKSOURCE_HSE;//RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;//RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;//RCC_HCLK_DIV4;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }


  if(vers==1){
      PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C1|RCC_PERIPHCLK_USB
                                  |RCC_PERIPHCLK_ADC;
      PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
      PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;//RCC_ADCCLKSOURCE_SYSCLK
      PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
      PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;
      PeriphClkInit.PLLSAI1.PLLSAI1M = 6;
      PeriphClkInit.PLLSAI1.PLLSAI1N = 20;
      PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK;
  }
  if(vers==2){
      PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C1|RCC_PERIPHCLK_ADC;
      PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_SYSCLK;//RCC_I2C1CLKSOURCE_PCLK1;
      PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;//RCC_ADCCLKSOURCE_PLLSAI1;//RCC_ADCCLKSOURCE_SYSCLK
      PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;
      PeriphClkInit.PLLSAI1.PLLSAI1M = 6;
      PeriphClkInit.PLLSAI1.PLLSAI1N = 20;
      PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2;
      PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_ADC1CLK;
  }


  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }

  //__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_4);
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
  __HAL_FLASH_DATA_CACHE_ENABLE();

}

I added 4 Flash wait states according to the reference manual. Now we reach the funny part. My original code was change into the BSRR register (commented in the loop). I noticed this line takes about 30 tics to execute. So I made an array "lineindex" with all 32bit values to be written in BSRR, no calculations required; Oscilloscope on the lowest pin gave me this: enter image description here

I was surprised by the low value. Run from flash/RAM brought like 100-200kHz difference. So spent 2 days studying how to do the same using DMA. Now I use TIM1 to trigger DMA to make MEM2PERIPH transfer (MEM source is the 32bit word array, sending data through DMA directly to BSRR). If I get it right MCU is not used during the burst sends (160transfers at once), manual says bus can run on 120MHz (set at 100MHz now), so... what happens? Code:

//Timer
static void MX_TIM1_Init(void)
{


    /* 3 (Timer TIM1) */
    __HAL_RCC_TIM1_CLK_ENABLE();
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 7;
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;
    HAL_TIM_Base_Init(&htim1);
      /* 7 (Enable TIM for DMA events) */
    __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

}


// DMA2 (includes ADC code on channel3, channel6 is used for the current loop)
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMAMUX1_CLK_ENABLE();
  __HAL_RCC_DMA2_CLK_ENABLE();
  __DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  hdma_adc1.Instance = DMA2_Channel3;
  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_adc1.Init.Mode = DMA_CIRCULAR;
  hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
  //HAL_DMA_DeInit(&hdma_adc1);
  HAL_DMA_Init(&hdma_adc1);
  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

  DMAMUX1_Channel9->CCR &= ~( DMAMUX_CxCR_DMAREQ_ID );
  DMAMUX1_Channel9->CCR = 0x5;


    /* 4 (DMA2 Channel6) */
    hdma_tim1_uev.Instance = DMA2_Channel6;
    hdma_tim1_uev.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_tim1_uev.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim1_uev.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim1_uev.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // 16 bits
    hdma_tim1_uev.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_tim1_uev.Init.Mode = DMA_NORMAL;
    hdma_tim1_uev.Init.Priority = DMA_PRIORITY_LOW;
    HAL_DMA_Init(&hdma_tim1_uev);
    __HAL_LINKDMA(&htim1,hdma[TIM_DMA_ID_UPDATE],hdma_tim1_uev);

    DMAMUX1_Channel12->CCR &= ~( DMAMUX_CxCR_DMAREQ_ID );
    DMAMUX1_Channel12->CCR = 46U;

  //DMAMUX1_Channel9->CCR &= ~( DMAMUX_CxCR_DMAREQ_ID );
  //DMAMUX1_Channel9->CCR |=  ( 0x8 << DMAMUX_CxCR_DMAREQ_ID_Pos );




  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Channel3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Channel3_IRQn);

  HAL_NVIC_SetPriority(DMA2_Channel6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Channel6_IRQn);

  /* DMAMUX1_OVR_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);

}



// Loop in main
for (counteri=1;counteri<100000;counteri++){
      HAL_DMA_Start_IT(htim1.hdma[TIM_DMA_ID_UPDATE],(uint32_t) lineindex,(uint32_t)&GPIOF->BSRR, 160);

      __HAL_TIM_ENABLE(&htim1);

      while((TIM1->CR1 & 1U));

}


What I get as speed is: enter image description here

I know the timer Period is a magic number. Changed timer speed from like 5MHz to 50MHz. The shown magic number leads to the maximum loop speed of 6MHz I got, so just left it there.

Now the questions. What am I doing wrong? In other posts I saw reasonable speeds of 15-20MHz with pin toggle function. Direct writing in the registers and even excluding the MCU did not help. I guess it is either some clock error I don't see, or just bus waiting, but it looks strange since there is nothing else going there at the moment. I noticed that in debug mode one asm("NOP"); takes 4 tics. No change regardless whether I run from Flash or RAM (thought it is wait states at first). On slower clock I couldn't even reach 1MHz. Also, big worry is the transient peaks. -0.7~-0.8V to 4V? I wonder whether this is some oscilloscope settings issue or it is normal?

To add more, the code bellow is used since I read analog data from the sensor:

for (counteri=1;counteri<100000;counteri++){

    for(uint8_t vc = 1; vc < 321; vc++){

        GPIOF -> BSRR = lineindex[vc-1];//(vc) | (((~vc) & 0xFF) << (16)); //col

                    asm("NOP");

                    ADC1->CR =268435461U;//|= 4U;                                                    // start ADC reading
                while(!(ADC1->ISR & ADC_ISR_EOS));                                 // wait for ADC conversion to complete
            ADC1->ISR &= 14U;
    }

}

More magic numbers, but tried to eliminate all possible calculations even when values are written in registers. So, with ADC, reading 4 channels using DMA, at 80MHz, 2.5cycles sampling, 12bit, I got less than 500kHz. ADC should be 5.88Msps, 4 channels with some overhead, I would expect at least double, but please if you have any ideas correct me: enter image description here

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Can you provide jnfo on the sensor? Most sensors have a mode to shift data out without always readdressing, so this sounds like a suboptimal usage to me. \$\endgroup\$ Aug 16, 2021 at 13:19
  • 2
    \$\begingroup\$ IN your code // Loop in main ... for (counteri=1;counteri<100000;counteri++){ ... it looks like you're waiting for the timer with while((TIM1->CR1 & 1U));. But surely you should be waiting for the DMA to complete rather than the timer? Each timer event will trigger a single DMA transfer, so as you have it now it seems to me that you only get a single DMA transfer before restarting it over again. \$\endgroup\$
    – brhans
    Aug 16, 2021 at 13:38
  • \$\begingroup\$ @ScottSeidman It is an image sensor. There is no such option for it. I need to send the addresses and wait for a short time until the data output is set (this is why i have the NOP part). \$\endgroup\$
    – D. K.
    Aug 17, 2021 at 11:38
  • \$\begingroup\$ @brhans You are correct that DMA would be logical choice. My lack of understanding how to use callback functions led to that. In my callback I actually disable the timer. This is why in the main I just wait for it to stop to return back to main. Not sure how to do it otherwise. My future idea is to make the MCU make some calculations of the received values while TIM/DMA/ADC gather the next patch, so at some point MCU finishes and waits for the callback to reset the DMA for a new batch and start new calculations.... \$\endgroup\$
    – D. K.
    Aug 17, 2021 at 11:44

1 Answer 1

2
\$\begingroup\$

50 MHz is the maximum of the GPIO via a timer output.
10 MHz is the maximum when running bare assembler from ram (10 instructions).

What speed can you get in circular DMA with two transactions no triggers?
I guess about 25 Mhz, maybe slower. (4 bus ops)

\$\endgroup\$
4
  • \$\begingroup\$ Thank you for the clarifications! So, bare assembler from ram would always take 10 instructions? Is it related to the number of pins I toggle? From there I scale by the clock for ideally max speed possible? Is the same 4 bus ops defined too? I guess that may be the limitation that stops me... with overhead 6MHz+ sounds reasonable. Is this typical calculation for this MCU or like general rule? \$\endgroup\$
    – D. K.
    Aug 17, 2021 at 12:37
  • \$\begingroup\$ Actually, you made me think... When you say 50MHz with timer you mean something like PWM signal, right? Not register access or something. \$\endgroup\$
    – D. K.
    Aug 17, 2021 at 13:20
  • \$\begingroup\$ @D.K. 50 Mhz is the maximum speed the IO can work in due to the APB. You can only change the output bit every 1/100 Mhz, so half that is the maximum toggle. The 4 bus ops is, read-const, write-register, read-const, write-register for two edges. That is also limited to the 100 MHz APB clock. The 10 instructions is just how much it takes on average, loop, read const, load address, write register, branch loop. \$\endgroup\$
    – Jeroen3
    Aug 17, 2021 at 13:44
  • \$\begingroup\$ Thank you! So I may play a little bit trying to push from 6MHz to closer to 10MHz. Or just use the max possible speed of 120MHz... At the end, nothing strange about the code, maybe just overhead. \$\endgroup\$
    – D. K.
    Aug 17, 2021 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.