G474 SPI DMA sync occasionally missed?
I have a program where I have TIM2 CH1 set up in output compare mode. Approximately 0.5 ms after the timer is reset, CH1's compare value is reached. This triggers the CH1 DMA, which is a dummy DMA on DMA1 channel 1. The SPI TX DMA is synced to the TIM2_CH1 DMA (with a request count of 16, to allow 16 bytes through the SPI), and therefore if it is armed should automatically start at that moment. The purpose of this is to eventually provide an interrupt-free delay for an external SPI slave that requires a 350ns delay between CSn active and SCLK active.
I have this all mostly working, but my problem is that about 5% of the time, the SPI DMA does not complete. I can't figure out what in my code is allowing this to happen. The basic structure is:
- Arm SPI DMAs.
- Reset TIM2 counter.
- Wait until the DMA completes. (This should be triggered by TIM2 reaching the compare value.
However, for reasons I cannot understand, about 5-10% of my transfers never appear to start -- I wait for a long period of time after resetting the timer, and even after that, nothing happens. I've tried 1000ms, but the code I'll show below uses 10ms. The results are much the same either way.
Resetting the timer again solves the problem. I would assume that this indicates that the timer compare event was missed somehow, but the delay of 500usec should make this impossible, and anyway, the SPI DMAs are armed before the timer is even reset.
Another funny symtom I've noticed, that I can't explain. I twiddle PA4 in the process of this code to help trigger my oscilloscope. Usually, I can see the SPI clock associated with PA4 going high, and PA4 goes low sometime later (caused by resetting the pin in the TxRxCpltCallback). But sometimes, I see the SPI SCLK ticking without an associated high state of the PA4 GPIO.
Here's the main body of the program:
/* USER CODE BEGIN 4 */
osSemaphoreId_t tx_cmplt_event;
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
LL_GPIO_SetOutputPin(GPIOA, GPIO_PIN_4);
}
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
spi_dma_in_flight = 0;
spi_dma_success_count++;
LL_GPIO_ResetOutputPin(GPIOA, GPIO_PIN_4);
osSemaphoreRelease(tx_cmplt_event);
}
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
spi_dma_in_flight = 0;
spi_last_error_code = hspi->ErrorCode;
spi_dma_error_count++;
osSemaphoreRelease(tx_cmplt_event);
}
}
void Tim2Ch1DmaComplete(DMA_HandleTypeDef* hdma) {
}
/* USER CODE END 4 */
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @PAram argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
osSemaphoreAttr_t semaphore_def = {
.name = "tx_cmplt_event",
};
tx_cmplt_event = osSemaphoreNew(1, 0, &semaphore_def);
if (HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_1) != HAL_OK) {
Error_Handler();
}
LL_TIM_OC_SetMode(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM2);
int successful_transfers = 0;
int timeouts = 0;
for (;;) {
static uint8_t spi_data[16];
static uint8_t spi_rx_data[16];
spi_data[0] = 0x55;
spi_rx_data[0] = 0xF0;
if (HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY ) {
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive_DMA(&hspi1, spi_data, spi_rx_data, sizeof(spi_data));
if (status != HAL_OK) {
spi_dma_error_count++;
}
}
osDelay(1);
LL_TIM_SetCounter(TIM2, 0);
LL_GPIO_ResetOutputPin(GPIOA, GPIO_PIN_4);
// Set up the SPI to send 0x55 over DMA.
if (osSemaphoreAcquire(tx_cmplt_event, 10) == osErrorTimeout) {
// If we time out waiting for the SPI transfer to complete, that's a problem.
timeouts += 1;
} else {
successful_transfers += 1;
}
}
/* USER CODE END 5 */
}The entire project can be cloned from here. Particularly you might be interested in lookign at the whole main.c or stm32g4xx_hal_msp.c. Let me know if you spot what's wrong or have ideas where I can look.
