# STM32L4 slow toggling GPIO pins

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:

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
PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
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;
}
if(vers==2){
PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_SYSCLK;//RCC_I2C1CLKSOURCE_PCLK1;
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;
}

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:

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 */

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);

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:

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");

}

}


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:

• 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. Aug 16, 2021 at 13:19
• 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. Aug 16, 2021 at 13:38
• @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). Aug 17, 2021 at 11:38
• @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.... Aug 17, 2021 at 11:44