Skip to main content
Explorer II
March 27, 2025
Question

Switching from HAL to LL complications

  • March 27, 2025
  • 4 replies
  • 1126 views

Good day,

I recently made a post about getting my SPI to a certain speed. Ive managed to get the core down but found out that HAL is way too slow for what I need. I thus switched to low-level drivers. Now using LL instead of hal, Im trying to do a simple transmit receive to my 24 bit SPI. While HAL used to work, I'm doing something wrong. me and my collegue can't put our fniger on it. Anyone here think they might be able to help figure it out? Code is added below. many thanks in advance!

Board is the STM32 H755ZIQ. 
Also before this comes up, yes I will likely have to do this through DMA eventually. This will have to do for now though.

static void MX_SPI1_Init(void)
{

 /* USER CODE BEGIN SPI1_Init 0 */

 /* USER CODE END SPI1_Init 0 */

 LL_SPI_InitTypeDef SPI_InitStruct = {0};

 LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
 RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

 /** Initializes the peripherals clock
 */
 PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI1;
 PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL;
 if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
 {
 Error_Handler();
 }

 /* Peripheral clock enable */
 LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);

 LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOA);
 LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOG);
 /**SPI1 GPIO Configuration
 PA6 ------> SPI1_MISO
 PA7 ------> SPI1_MOSI
 PG11 ------> SPI1_SCK
 */
 GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7;
 GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
 GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
 GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
 GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
 GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
 LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

 GPIO_InitStruct.Pin = LL_GPIO_PIN_11;
 GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
 GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
 GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
 GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
 GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
 LL_GPIO_Init(GPIOG, &GPIO_InitStruct);

 /* SPI1 interrupt Init */
 NVIC_SetPriority(SPI1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
 NVIC_EnableIRQ(SPI1_IRQn);

 /* USER CODE BEGIN SPI1_Init 1 */

 /* USER CODE END SPI1_Init 1 */
 /* SPI1 parameter configuration*/
 SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
 SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
 SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_24BIT;
 SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
 SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
 SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
 SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2;
 SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
 SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
 SPI_InitStruct.CRCPoly = 0x0;
 LL_SPI_Init(SPI1, &SPI_InitStruct);
 LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
 LL_SPI_SetFIFOThreshold(SPI1, LL_SPI_FIFO_TH_01DATA);
 LL_SPI_DisableNSSPulseMgt(SPI1);

 LL_SPI_Enable(SPI1);
 /* USER CODE BEGIN SPI1_Init 2 */

 /* USER CODE END SPI1_Init 2 */

}

void SPI1_TransmitReceive() {
		 while (LL_SPI_IsActiveFlag_TXP(SPI1) == 0); // Wait until the TX FIFO is not full (TXP)
 LL_SPI_TransmitData8(SPI1, (TxData >> 16) & 0xFF); // MSB
 while (LL_SPI_IsActiveFlag_TXP(SPI1) == 0); // Wait until the TX FIFO is not full (TXP)
 LL_SPI_TransmitData8(SPI1, (TxData >> & 0xFF); // Middle byte
 while (LL_SPI_IsActiveFlag_TXP(SPI1) == 0); // Wait until the TX FIFO is not full (TXP)
 LL_SPI_TransmitData8(SPI1, TxData & 0xFF); // LSB

 // Ensure all bytes are transmitted before reading RX
 while (!LL_SPI_IsActiveFlag_EOT(SPI1)); // End Of Transfer flag
 // Wait until we have three bytes in RX FIFO
 while (LL_SPI_GetRxFIFOPackingLevel(SPI1) < 3);

 uint32_t receivedData = 0;

 while (LL_SPI_GetRxFIFOPackingLevel(SPI1) == 0);
 receivedData |= (LL_SPI_ReceiveData8(SPI1) << 16); // MSB
 while (LL_SPI_GetRxFIFOPackingLevel(SPI1) == 0);
 receivedData |= (LL_SPI_ReceiveData8(SPI1) << 8); // Middle byte
 while (LL_SPI_GetRxFIFOPackingLevel(SPI1) == 0);
 receivedData |= LL_SPI_ReceiveData8(SPI1); // LSB

 RxData = receivedData;

 HAL_Delay(10); // Remove this delay in real-time applications
}

/* Stop TIM1 */
void Stop_TIM1(void)
{
 /* Stop TIM1 base timer */
 if (HAL_TIM_Base_Stop_IT(&htim1) != HAL_OK)
 {
 /* Error Handling */
 Error_Handler();
 }
}

/* Start TIM1 in interrupt mode */
void Start_TIM1(void)
{
	HAL_NVIC_SetPriority(TIM1_UP_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
	sampling = 1;
 /* Start TIM1 base timer with interrupt */
 if (HAL_TIM_Base_Start_IT(&htim1) != HAL_OK)
 {
 /* Error Handling */
 Error_Handler();
 }
}

void TIM1_UP_IRQHandler(void)
{
 HAL_TIM_IRQHandler(&htim1);
}

///* Timer Interrupt Handler */
//void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
//{
// if (htim->Instance == TIM1) // Check if TIM1 triggered the interrupt
// {
// /* Code to execute on every timer interrupt */
// 	// This code should be executed every time TIM2 period elapses
// 	countertest++;
// 	four = 4; // This should set 'four' to 4 every time the timer triggers
// }
//}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	countertest++;
	four = 4;
 if (htim->Instance == TIM1) {
 if (sampleIterator >= maxIterator) {
 sampleIterator = 0;
 }

 currentSample++;

 // Select SPI transfer data
 TxData = txDataList[sampleIterator];

 // Toggle CNV to trigger ADC conversion
 GPIOC->BSRR = GPIO_BSRR_BS10; // Set CNV (pin 10 high)
 GPIOC->BSRR = GPIO_BSRR_BR10; // Reset CNV (pin 10 low)

 // Wait until C11 is low (ADC ready)
 while (GPIOC->IDR & GPIO_IDR_ID11);

 // Pull CS low
 GPIOC->BSRR = GPIO_BSRR_BR12;

 // Perform SPI transfer
 //HAL_SPI_TransmitReceive(&hspi1, (uint32_t*) &TxData, (uint32_t*) &RxData, 1, HAL_MAX_DELAY);
 SPI1_TransmitReceive();

 // Pull CS high
 GPIOC->BSRR = GPIO_BSRR_BS12;

 // Extract 18-bit ADC value
 adc_value = (RxData >> 6) & 0x3FFFF;

 // Store data in active buffer
 currentBuffer[bufferIndex++] = adc_value & 0xFF; // LSB
 currentBuffer[bufferIndex++] = (adc_value >> 8) & 0xFF; // Middle byte
 currentBuffer[bufferIndex++] = (adc_value >> 16) & 0xFF;// MSB

 // When buffer is full, switch buffers and send data
 if (bufferIndex >= BUFFER_SIZE) {
 bufferIndex = 0;

 if (!sendingInProgress) {
 sendingInProgress = 1;
 uint8_t* temp = currentBuffer;
 currentBuffer = sendingBuffer;
 sendingBuffer = temp;

 CDC_Transmit_HS((uint8_t*)sendingBuffer, BUFFER_SIZE);
 }
 }

 if (currentSample >= totalSamples) {
 sampling = 0;
 }

 sampleIterator++;
 }
}

 

 

 

 

    This topic has been closed for replies.

    4 replies

    Technical Moderator
    March 27, 2025

    Hello @Igneous ,

    Please, instead of sharing screenshots of your code, please use </> button and past it here.

    See this link.

    Thank you for your understanding.

    IgneousAuthor
    Explorer II
    March 27, 2025

    Thanks for the note, ill keep it in mind for next time and ill edit it in the post

    Super User
    March 27, 2025

    Please see How to insert source code - not as images.

     

    What problem(s) are you having with your LL code?

    IgneousAuthor
    Explorer II
    March 27, 2025

    The code seems to get stuck in the transmitreceive method Ive written. For some reason it appears that, once it gets to the RX section of this code, it hangs on the RX flags and never passes them.

    IgneousAuthor
    Explorer II
    March 27, 2025

    Update:
    I've changed some of the code. I now have a responce in my logic analyser that I can see. Problem is that it's not being read as rxdata for some reason and still getting stuck in the loop on [while (!LL_SPI_IsActiveFlag_EOT(SPI1));]

    Logic analyser:

    Igneous_0-1743080461150.png

     

    Current method:

    void SPI1_TransmitReceive() {
    	three = 1;
    	if (LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_6)){
    		// Ensure SPI is enabled
    		if (!LL_SPI_IsEnabled(SPI1)) {
    			LL_SPI_Enable(SPI1);
    		}
    
    		// Pull NSS (PC12) LOW to start transaction
    		LL_GPIO_ResetOutputPin(GPIOC, LL_GPIO_PIN_12);
    		LL_SPI_StartMasterTransfer(SPI1);
    
    		// Start Master Transfer (Needed for STM32H7)
    		LL_SPI_StartMasterTransfer(SPI1);
    		three = 2;
    		// Wait until TX is ready
    		while (!LL_SPI_IsActiveFlag_TXP(SPI1));
    
    		// Send 24-bit data
    		LL_SPI_TransmitData32(SPI1, TxData);
    		three = 3;
    		rx_fifo_level = LL_SPI_ReceiveData32(SPI1);
    		// Wait for End of Transfer
    		while (!LL_SPI_IsActiveFlag_EOT(SPI1));
    		three = 4;
    		// Clear EOT flag
    		LL_SPI_ClearFlag_EOT(SPI1);
    
    		// Wait for RX data
    		while (!LL_SPI_IsActiveFlag_RXP(SPI1));
    
    		// Read received 24-bit data
    		RxData = LL_SPI_ReceiveData32(SPI1);
    
    		// Pull NSS (PC12) HIGH to end transaction
    		LL_GPIO_SetOutputPin(GPIOC, LL_GPIO_PIN_12);
    	}
    }



    IgneousAuthor
    Explorer II
    April 1, 2025

    While I have not yet found the solution, I have found that the speed might just be enough this way. However I might instead just start directly writing to the register to make it as optimal as possible. This week im occupied with other work but I will update when/if I have more info on how this is going.

    Explorer
    April 1, 2025

    > However I might instead just start directly writing to the register to make it as optimal as possible.

    Agreed, the difference to LL code is relatively small, including the additional effort.

    But I would recommend consult the SPI section of reference manual in this regard in detail .
    I had ported a driver for SPI sensor device from a C032 to a F407 MCU not so long ago, and the SPI peripherals on both cores had some significant differences in behavior (flags).
    The Cube example code is often very "hulky" in this regard, using busy-waits for flags.

    IgneousAuthor
    Explorer II
    April 1, 2025

    This is part of my problem, yes. I can verify the data is sent and received on my data analyzer, but the data simply isn't where I'm expecting it. I wouldn't be surprised if it's an issue with a flag not being toggled or something. Wish this particular datasheet was less confusing, haha. I'll take a look into it. The overhead with HAL is the big issue with fast stuff like this. 1MS/s is hard to achieve when Hal takes 2-3 us between toggling the start of conversion and the actual conversion happening.