我如何 select STM32 的外部中断时间短?

how do I select an STM32 for low external interrupt time?

我有一个电路需要在大约 0.5uS 内响应外部中断。我用一个 STM32F031K6 和一个在 2x PLL 上设置为 运行 的 20MHz 振荡器构建了电路,提供了一个 40MHz 的时钟。我很惊讶地发现,虽然一个时钟周期是 25nS,但我只能以 300nS 切换一个引脚——我不确定为什么要花这么长时间,我对 8 位 AVR 有一些经验,虽然我不希望它运行 在一个时钟周期内,12 似乎很慢。外部中断需要 3uS 响应。如何选择芯片来满足我对0.5uS的要求?

我只是假设我需要更换芯片,如果有人对我如何减少响应时间有建议,那也很好

我的完整代码在这里,这是一个由 cube 生成的空白程序,我删除了一些生成的注释以使其更易于阅读



int main(void)
{
  MX_GPIO_Init();
  MX_ADC_Init();

  while (1)
  {
  }
}

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

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI14|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSI14State = RCC_HSI14_ON;
  RCC_OscInitStruct.HSI14CalibrationValue = 16;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

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

static void MX_ADC_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel to be converted. 
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel to be converted. 
  */
  sConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 
                          |GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA2 PA11 */
  GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_11;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PA3 PA4 PA12 PA15 */
  GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_12|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PA6 PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.Alternate = GPIO_AF1_TIM3;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : PB0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.Alternate = GPIO_AF1_TIM3;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pins : PB1 PB3 PB4 PB5 
                           PB6 PB7 */
  GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5 
                          |GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pins : PA8 PA9 PA10 */
  GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);

  HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);

}

void Error_Handler(void)
{
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

void EXTI2_3_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}

void EXTI4_15_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
  GPIOB->ODR ^= 1<<1;
}

Cortex-M 处理器在异常进入时推送堆栈帧,对于您的 Cortex M0,从异常断言到 运行 中断处理程序的第一条指令的最短时间是 16 个时钟周期(假设为零等待状态内存)。

缩短此过程的唯一方法是使用更高的时钟速度。

首先,我建议您查看 this ARM blog post 以深入了解 ARM Cortex-M 处理器的中断延迟。

正如@Colin 所提到的,具有 Cortex-M0 内核的 STM32F0 MCU 的中断延迟是 16 个时钟周期,从 EXTI 线上的信号被断言开始直到进入 IRQ 处理程序,代码对事件做出反应。固件无法减少此时钟周期数。 When selecting a MCU with a Cortex-M3 or M4 core (e.g. a STM32F3), this required number of clock cycles drops to 12. The resulting latency still depends on the clock frequency of the core.选择具有更高最大值的 STM32 MCU。时钟速率允许更快的反应时间:

  • STM32F0:高达 48 MHz Cortex M0 => ISR 进入延迟 333 ns
  • STM32G0:高达 64 MHz Cortex M0+ => ISR 进入延迟 234 ns
  • STM32F3:高达 72 Mhz Cortex M4 => ISR 输入延迟 166 ns
  • STM32G4:高达 170 Mhz Cortex M4 => ISR 输入延迟 70 ns

这些计算并不能解决您的问题,因为我们还必须考虑在MCU 进入服务程序后会发生什么。我想到了几件事:

  1. 根据 ISR 的复杂性备份额外的寄存器
  2. 应用程序对事件的特定反应代码(例如切换 GPIO)
  3. 闪存/RAM/外设访问的等待状态。核心时钟越高,通常需要越多的等待状态,因为外部部件的时钟频率越低。
  4. 确认/清除中断请求代码

最后一点可以推迟到应用程序特定响应之后,因此不一定计入反应时间,但所有其他点都会产生重大影响。为了使用经济高效的 STM32 MCU 满足您的要求(我想您是出于这个原因选择了 STM32F0),您需要很好地控制 ISR 中的指令数量。我不建议在这里使用汇编程序,但你不应该依赖 CubeMX HAL 实现。

既然你说你在 ISR 中只需要很少的代码,那么让我们快速估算一下:

  1. 在堆栈上保存两个额外的寄存器 => 2 条指令
  2. 使用读取-修改-写入序列切换 GPIO => 3 条指令

假设每条指令需要2个周期,我们还需要10个时钟周期。 使用这个最好的情况,我们可以再次查看我们的列表,其中包含相当低成本的 STM32 MCU:

  • STM32F0:48 MHz 时 16 + 10 个周期 => 541 ns
  • STM32G0:64 MHz 时 15 + 10 个周期 => 390 ns
  • STM32F3:72 MHz 时 12 + 10 个周期 => 300 ns
  • STM32G4:170 MHz 时 12 + 10 个周期 => 130 ns

根据这些数字,Cortex M0/M0+ 看起来不是正确的选择。您最好选择时钟频率至少为 64 MHz 的 M3/M4 内核。我认为新的 G4 可能是一个很好的解决方案。

无论如何,我强烈建议根据实际需求评估实际性能,因为有太多因素会影响上述延迟计算。