2025-10-29 8:13 AM - last edited on 2025-10-29 8:34 AM by Andrew Neil
Hi ST community,
I am facing an issue of 10us delay between CS line going low and SPI SCK start, when I am doing SPI communication with a slave device, which supports upto 15MHz SPI communication.
Below is the main.c file snippet for your reference. Please help in fixing the issue. I have also attached complete code file as well as the CRO capture of SPI comm for your ref. Thanks in advance!
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdint.h>
#include <string.h> // For memset
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi1_rx;
// Global variable for the master's Gray Counter
volatile uint8_t header_gray_code = 0;
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;
TIM_HandleTypeDef htim16;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_SPI1_Init(void);
static void MX_TIM16_Init(void);
/* USER CODE BEGIN PFP */
// Define your Pins and Ports (assuming PA4=NSS, PA5=Red LED, PA9=LD2)
#define NSS_GPIO_PORT GPIOA
#define NSS_GPIO_PIN GPIO_PIN_4
#define RED_LED_PORT GPIOA
#define RED_LED_PIN GPIO_PIN_5
#define LD2_GPIO_PORT GPIOA
#define LD2_PIN GPIO_PIN_9
// Global Error Tracking Variables
#define MAX_CRC_ERRORS 1
static volatile uint8_t crc_error_count = 0;
static volatile uint8_t spi_communication_discarded = 0;
// Delay generation using TIM16
#define TIM16_PRESCALER 1 // Minimum delay generated = 1/(80M) = 12.5 ns
// Command Arguments
#define CMD_WRITE 0x00
#define CMD_READ 0x01
// Global Gray counter
static uint8_t Header_Counter = 0;
// Global DMA Buffers (6 bytes for 48-bit frame)
#define FRAME_SIZE_BYTES 6
static uint8_t tx_frame_buffer[FRAME_SIZE_BYTES];
static uint8_t rx_frame_buffer[FRAME_SIZE_BYTES];
volatile uint32_t sent_data_for_comparison = 0; // Data is now 32-bit
/* Custom CRC-6 Implementation */
#define CRC6_POLY 0x33 // 0x33 (0b110011)
#define CRC6_INIT 0x3F // 0x3F (0b111111)
#define CRC6_WIDTH 6
#define CRC_DATA_BITS 42 // 6 (Addr) + 2 (Cmd) + 2 (Hdr) + 32 (Data)
/**
* @brief Calculates the custom CRC-6 for a 42-bit input data.
* data_42_bits The 42 bits of data to calculate CRC over, passed as uint64_t.
* @retval The 6-bit CRC value.
*/
uint8_t calculate_crc6(uint64_t data_42_bits) {
const uint8_t POLYNOMIAL = 0x33; //
const uint8_t INITIAL_VALUE = 0x3F; // 6-bit seed (binary 111111)
uint8_t crc6 = INITIAL_VALUE;
// Process each bit from MSB (bit 26) down to LSB (bit 0)
for (int i = 41; i >= 0; i--) {
uint8_t bit = (data_42_bits >> i) & 0x01; // Extract the bit at position i
uint8_t crcMsb = (crc6 >> 5) & 0x01; // MSB of the 6-bit CRC
// Shift left by 1 bit and keep CRC6 of 6 bits only
crc6 = (crc6 << 1) & 0x3F;
// XOR condition: If bit differs from CRC MSB
if ((bit ^ crcMsb) != 0) {
crc6 ^= POLYNOMIAL;
crc6 &= INITIAL_VALUE; // Ensure CRC6 holds last 6 bits
}
}
return crc6;
}
uint8_t get_next_gray_code(void)
{
// Gray counter logic remains the same (0, 1, 3, 2)
uint8_t binary_counter = (Header_Counter + 1) % 4;
Header_Counter = binary_counter;
return Header_Counter ^ (Header_Counter >> 1);
}
/**
* @brief Constructs the 48-bit frame and serializes it into the 6-byte TX buffer (MSB first).
* Frame format (Bit 47 to Bit 0): [Addr(6)] [Cmd(2)] [Hdr(2)] [Data(32)] [CRC6(6)]
*/
void construct_spi_frame_8bit(uint8_t reg_address, uint8_t command_arg, uint32_t data_bits)
{
uint64_t temp_frame_48bit = 0;
uint64_t data_for_crc = 0;
uint8_t header_gray_code = get_next_gray_code();
// 1. Assemble the 42 data bits for CRC calculation (Bits 47 down to 6)
// Addr (6 bits) -> Bits 47-42
temp_frame_48bit |= ((uint64_t)(reg_address & 0x3F) << 42);
// Cmd (2 bits) -> Bits 41-40
temp_frame_48bit |= ((uint64_t)(command_arg & 0x03) << 40);
// Hdr (2 bits) -> Bits 39-38
temp_frame_48bit |= ((uint64_t)(header_gray_code & 0x03) << 38);
// Data (32 bits) -> Bits 37-6
temp_frame_48bit |= ((uint64_t)(data_bits & 0xFFFFFFFF) << 6);
// Extract only 42 bits for CRC calculation (bits 47 down to 6)
data_for_crc = (temp_frame_48bit >> 6) & ((1ULL << 42) - 1);
// 3. Calculate CRC
uint8_t crc_value = calculate_crc6(data_for_crc);
// 4. Append CRC (6 bits) - Bit 5 down to 0
temp_frame_48bit |= (uint64_t)(crc_value & 0x3F);
// 5. Break the 48-bit frame into 6 bytes, MSB-first (Big-Endian)
// The SPI master sends Byte 5 first (MSB)
tx_frame_buffer[0] = (uint8_t)((temp_frame_48bit >> 40) & 0xFF); // Byte 5 (Bits 47-40)
tx_frame_buffer[1] = (uint8_t)((temp_frame_48bit >> 32) & 0xFF); // Byte 4 (Bits 39-32)
tx_frame_buffer[2] = (uint8_t)((temp_frame_48bit >> 24) & 0xFF); // Byte 3 (Bits 31-24)
tx_frame_buffer[3] = (uint8_t)((temp_frame_48bit >> 16) & 0xFF); // Byte 2 (Bits 23-16)
tx_frame_buffer[4] = (uint8_t)((temp_frame_48bit >> & 0xFF); // Byte 1 (Bits 15-8)
tx_frame_buffer[5] = (uint8_t)(temp_frame_48bit & 0xFF); // Byte 0 (Bits 7-0)
}
/* MODIFIED Full-Duplex DMA Transfer Function ----------------------------------*/
HAL_StatusTypeDef spi_transmit_receive_dma_frame(uint8_t reg_address, uint8_t command_arg, uint32_t data_bits)
{
if (spi_communication_discarded)
{
return HAL_ERROR;
}
if (reg_address >= 49) // Check for valid register address
{
return HAL_ERROR;
}
// 1. Store the 32-bit data for comparison
sent_data_for_comparison = data_bits;
// 2. Construct the 48-bit frame into the 8-bit TX buffer (6 bytes)
construct_spi_frame_8bit(reg_address, command_arg, data_bits);
// 3. Clear the RX buffer
memset(rx_frame_buffer, 0, sizeof(rx_frame_buffer));
// 4. Pull NSS low
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_RESET);
// delay(3); //Delay equal to 12.5*3 = 37.5ns
// 5. Initiate the Full-Duplex DMA transfer. Size is 6 bytes.
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive_DMA(
&hspi1,
tx_frame_buffer,
rx_frame_buffer,
FRAME_SIZE_BYTES); // Transfer 6 bytes
return status;
}
/* DMA Callback Function (Success Path) - Logic remains similar -----------------*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
// 1. Pull NSS high to end the transaction and insert a delay of 500ns
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
// 2. Clear the red LED and reset error count on successful completion
//HAL_GPIO_TogglePin(RED_LED_PORT, RED_LED_PIN);
crc_error_count = 0;
// // 3. Reconstruct the received 48-bit frame from 6 bytes (MSB first)
// uint64_t received_frame_48bit = 0;
//
// received_frame_48bit |= ((uint64_t)rx_frame_buffer[0] << 40);
// received_frame_48bit |= ((uint64_t)rx_frame_buffer[1] << 32);
// received_frame_48bit |= ((uint64_t)rx_frame_buffer[2] << 24);
// received_frame_48bit |= ((uint64_t)rx_frame_buffer[3] << 16);
// received_frame_48bit |= ((uint64_t)rx_frame_buffer[4] << 8);
// received_frame_48bit |= rx_frame_buffer[5];
//
// // 4. Extract the 32-bit data (Bits 37 down to 6)
// uint32_t received_data = (uint32_t)((received_frame_48bit >> 6) & 0xFFFFFFFF);
//
// // 5. Compare the sent data with the received data
// if (received_data == sent_data_for_comparison)
// {
// // Data Match Action (e.g., toggle user LED for success)
// HAL_GPIO_TogglePin(LD2_GPIO_PORT, LD2_PIN);
// }
//
// sent_data_for_comparison = 0;
}
}
/* DMA Error Callback Function (Error Path) - Logic remains same ----------------*/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
if (hspi->ErrorCode & HAL_SPI_ERROR_CRC)
{
crc_error_count++;
if (crc_error_count >= MAX_CRC_ERRORS)
{
spi_communication_discarded = 1;
}
}
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
if (!spi_communication_discarded)
{
hspi->State = HAL_SPI_STATE_READY;
}
}
}
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI1_Init();
MX_TIM16_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(100);
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, SET);
HAL_GPIO_WritePin(RED_LED_PORT, RED_LED_PIN, RESET);
// Test data (now 32-bit)
uint8_t reg_addr = 0x00; // Max 0x31 (decimal-49)
uint32_t test_data = 0x10FF0F0F;
uint8_t dummy_tx[1] = {0xFF};
uint8_t dummy_rx[1] = {0x00};
HAL_TIM_Base_Start(&htim16);
// If using Hardware NSS:
//HAL_SPI_TransmitReceive(&hspi1, dummy_tx, dummy_rx, 1, HAL_MAX_DELAY);
// If using Software NSS:
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, dummy_tx, dummy_rx, 1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (!spi_communication_discarded)
{
if (HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)
{
// Note: Data is 32-bit
spi_transmit_receive_dma_frame(reg_addr, CMD_READ, test_data);
delay(25); // delay of 312.5ns (Delay value is equal to (x*12.5)ns
}
}
else
{
HAL_Delay(500);
HAL_GPIO_TogglePin(RED_LED_PORT, RED_LED_PIN);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
Error_Handler();
}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = 64;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 10;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief SPI1 Initialization Function
* None
* @retval None
*/
static void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
// Mode 3 device : CPOL =1 (Idle High), CPHA = 1 (Rising edge data acquisition)
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
/**
* @brief TIM16 Initialization Function
* None
* @retval None
*/
static void MX_TIM16_Init(void)
{
/* USER CODE BEGIN TIM16_Init 0 */
/* USER CODE END TIM16_Init 0 */
/* USER CODE BEGIN TIM16_Init 1 */
/* USER CODE END TIM16_Init 1 */
htim16.Instance = TIM16;
htim16.Init.Prescaler = 20-1;
htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
htim16.Init.Period = 65535;
htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim16.Init.RepetitionCounter = 0;
htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM16_Init 2 */
/* USER CODE END TIM16_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
/* DMA1_Channel3_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}
/**
* @brief GPIO Initialization Function
* None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5|GPIO_PIN_9, GPIO_PIN_RESET);
/*Configure GPIO pin : PA4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA5 PA9 */
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
// This function will generate a delay equal to (0.25* quarter_us)
void delay(uint16_t quarter_us)
{
__HAL_TIM_SET_COUNTER(&htim16, 0);
while(__HAL_TIM_GET_COUNTER(&htim16)< quarter_us);
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* file: pointer to the source file name
* line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */Edited to apply source code formatting - please see How to insert source code for future reference.
2025-10-29 11:36 AM
It is possible to generate a CS signal using a timer channel, with that timer also triggering DMA requests, the DMA configured to do memory to SPI->DR transfer. Not likely to be achieved via CubeMX generation though.
2025-10-29 11:09 PM