STM32F1计时器更新中断中,轮询ADC卡死

文章讲述了在STM32F1平台中使用TIM1中断触发ADC采样时遇到的问题,涉及中断优先级设置、初始化顺序调整以及如何避免HAL_GetTick导致的卡顿。作者发现正确的初始化顺序和设置Systick优先级对解决此类问题至关重要。
摘要由CSDN通过智能技术生成

省流:中断优先级、初始化顺序,直接看总结。

起因

FOC中需要对两相电流采样。CubeMX中配置TIM1的 Update Event 作为ADC注入通道的外部触发源。想法是在TIM1的更新中断中完成电流环的计算,第一步是等待ADC采样完成。我的代码是这样的:

    // main中
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();
    MX_ADC1_Init();
    MX_ADC2_Init();
    MX_TIM1_Init();

    HAL_ADCEx_Calibration_Start(&hadc1);  // 校准 要在使能之前
    HAL_ADCEx_Calibration_Start(&hadc2);
    HAL_ADCEx_InjectedStart(&hadc1);  // 使能
    HAL_ADCEx_InjectedStart(&hadc2);
    HAL_TIM_Base_Start_IT(&htim1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
char buffer[50] = {0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) {
    const char msg[] = "waiting\r\n";
    HAL_UART_Transmit(&huart1, (uint8_t*)msg, sizeof(msg), 100);

    HAL_ADCEx_InjectedPollForConversion(&hadc1,1);
    int adc1 = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1);
    HAL_ADCEx_InjectedPollForConversion(&hadc2,1);
    int adc2 = HAL_ADCEx_InjectedGetValue(&hadc2, ADC_INJECTED_RANK_1);

    sprintf(buffer, "%d,%d,%d\r\n", htim1.Instance->CNT, adc1, adc2);
    HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 100);
}

同时在主循环中设置了LED1s切换一次亮灭。烧录……灯不闪,串口输出是waiting,说明卡死在了对ADC的轮询上。

配置检查

首先怀疑是否是我的配置有误。找了很多资料,但后来才发现其实(下图展示的这些)是没有问题的:

 此外,NVIC中开启了TIM1 update event的中断(未截图)。

深入函数内部

HAL_GetTick

配置检查不出问题,就看看到底哪里卡死了。去查函数HAL_ADCEx_InjectedPollForConversion。不喜欢打断点,就找了几个位置让串口发送数据(因为我用的是main.cpp所以没有重定向printf),最后发现:程序一直卡在①,进不去②,也出不去while。

// Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_adc_ex.c中的HAL_ADCEx_InjectedPollForConversion函数:
while(HAL_IS_BIT_CLR(hadc->Instance->SR, ADC_FLAG_JEOC))
{
    /* Check if timeout is disabled (set to infinite wait) */
    if(Timeout != HAL_MAX_DELAY)
    {
        // ①
        if((Timeout == 0U) || ((HAL_GetTick() - tickstart ) > Timeout))
        {
            // ②

于是在①的位置设置了输出HAL_GetTick()、tickstart、Timeout的串口,发现前两个都是0!只有可能HAL_GetTick()出问题了。为了辅助验证猜想,我将中断中的等待ADC换成了HAL_Delay,也是卡死。

深入HAL_GetTick一探究竟,并查了些资料,看来是一个利用系统时钟中断更新值的变量的访问入口。这说明这个变量没有被更新,说明系统时钟的中断无效。是不是中断优先级出问题了?

打开CubeMX,System tick timer优先级15。“优先级这么高只有它打断别人的份啊?”我疑惑着搜索了一下,才发现数字越小优先级越高。。。(乐)。。。将系统优先级设置为最高,果然不卡死了。

有个疑问:为什么默认systick优先级最低?搜索结果是担心其他外部中断响应不及时。

继续深入——初始化顺序

但是——进不去②是因为GetTick返回值不变,但出不去while就很奇怪了。于是我又跟踪了一下,跟踪使用的代码如下,依旧是修改了HAL_ADCEx_InjectedPollForConversion的内容:

  char msgs[50] = {0};  
  if ((hadc->Instance->JSQR & ADC_JSQR_JL) == RESET)
  {
    const char msg[] = "in if\r\n";
    HAL_UART_Transmit(&huart1, (uint8_t*)msg, sizeof(msg), 100);
    /* Wait until End of Conversion flag is raised */
    while(HAL_IS_BIT_CLR(hadc->Instance->SR, ADC_FLAG_JEOC))
    {
      sprintf(msgs, "while %d\r\n", HAL_IS_BIT_CLR(hadc->Instance->SR, ADC_FLAG_JEOC));
      HAL_UART_Transmit(&huart1, (uint8_t*)msgs, strlen(msgs), 100);
      /* Check if timeout is disabled (set to infinite wait) */
      if(Timeout != HAL_MAX_DELAY)
      {
        if((Timeout == 0U) || ((HAL_GetTick() - tickstart ) > Timeout))
        {
          /* New check to avoid false timeout detection in case of preemption */
          if(HAL_IS_BIT_CLR(hadc->Instance->SR, ADC_FLAG_JEOC))
          {
            /* Update ADC state machine to timeout */
            SET_BIT(hadc->State, HAL_ADC_STATE_TIMEOUT);

            /* Process unlocked */
            __HAL_UNLOCK(hadc);
            const char msgx[] = "timeout\r\n";
            HAL_UART_Transmit(&huart1, (uint8_t*)msgx, sizeof(msgx), 100);
            return HAL_TIMEOUT;
          }
        }
      }
    }
  }

串口输出结果:

waiting
in if
while 1
while 1
timeout
in if
while 1
while 1
timeout
0,0,0
waiting
in if
in if
35918,1950,0

发现第一次永远是timeout!

这就说明 while(HAL_IS_BIT_CLR(hadc->Instance->SR, ADC_FLAG_JEOC))出问题了。HAL_IS_BIT_CLR是一个宏定义,进行按位与,不修改内容,说明是hadc->Instance->SR出问题了。再结合只有第一次出问题,我猜测是初始化的问题。我把ADC相关的全部移到TIM1之前:

    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();

    MX_ADC1_Init();
    MX_ADC2_Init();
    HAL_ADCEx_Calibration_Start(&hadc1);  // 校准 要在使能之前
    HAL_ADCEx_Calibration_Start(&hadc2);
    HAL_ADCEx_InjectedStart(&hadc1);  // 使能
    HAL_ADCEx_InjectedStart(&hadc2);

    MX_TIM1_Init();
    HAL_TIM_Base_Start_IT(&htim1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

输出结果:

waiting
in if
in if
0,2079,0
waiting
in if
in if
35918,2318,0
waiting
in if
in if
82,2483,0
waiting
in if
in if
35918,2503,0
waiting
in if
in if
82,2598,0

第一次就正常了!(在串口输出“in if”的时候ADC就采样完成了,所以没有输出“while 1”)

太神奇了!现在把systick的优先级恢复到最低也可以用了(相当于禁用timeout,死等)。不过其中的原因(为什么将HAL_ADCEx_InjectedStart移到MX_TIM1_Init之前就可以了)没有搞清楚。

总结前的一点小问题

结果在文章foc配置篇——ADC注入组使用定时器触发采样的配置_hal_adcex_injectedstart-CSDN博客文章浏览阅读1.2w次,点赞54次,收藏276次。针对三电阻低测采样来讲一讲如何配置使用定时器来触发ADC的注入组进行采样_hal_adcex_injectedstarthttps://blog.csdn.net/jdhfusk/article/details/126201653

中, 我找到了不一样的初始化顺序——将HAL_TIM_Base_Start_IT移到最后。我也试了一下:

    MX_ADC1_Init();
    MX_ADC2_Init();
    MX_TIM1_Init();

    HAL_ADCEx_Calibration_Start(&hadc1);  // 校准 要在使能之前
    HAL_ADCEx_Calibration_Start(&hadc2);
    HAL_ADCEx_InjectedStart(&hadc1);  // 使能
    HAL_ADCEx_InjectedStart(&hadc2);
    
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);

    HAL_TIM_Base_Start_IT(&htim1);

一坨while输出简直就像是卡死了,但是我找到了这个:

while 1
while 1
while 1
while 1
while 1
while 1
while 1
while 1
while 1
while 1
in if
28,2483,0
waiting
in if
while 1
while 1
while 1
while 1
while 1
while 1
while 1
while 1
while 1

我把while1的输出删除,发现现在这个初始化顺序下,第一次 "in if" 等待的时间很久。还是不如“将ADC全部放到TIM初始化之前”来得快。

那……我之前是不是因为输出while导致看不到LED闪烁误判了?我又重新试了一下,发现没有搞错。

总结

1. systick的优先级默认是最低的,所以在中断内部不要轻易使用需要用到HAL_GetTick()的函数,比如HAL_Delay和HAL_ADCEx_InjectedPollForConversion。

2. 关于ADC和TIM的初始化顺序:

  • ADC_Init->ADC_Inject->TIM1_Init->TIM_IT->PWM 是表现最好的
  • ADC_Init->TIM1_Init->ADC_Inject->PWM->TIM_IT 会等很久
  • ADC_Init->TIM1_Init->ADC_Inject->TIM_IT->PWM 是不行的

CubeMX生成的代码永远将init放在最前面,因此“表现最好”的初始化顺序与CubeMX的顺序矛盾。综上,为了避免问题,还是老老实实用ADC的中断吧!在中断置标志位,在主函数轮询结果。在中断函数中,就应该避免费时的任何操作。

碎碎念:

我想用串口向FOC发送数据,如果FOC在主循环跑,万一跑一半发来一个串口中断(上位机的控制指令)怎么办呢?有三个办法:

1. 在中断里完成FOC算法。直接pass,宗旨不能违背,不然出一堆bug

2. FOC算法参数化,串口中断优先级设为较低,在TIM的更新中断中备份当前FOC参数,然后main中用备份的参数计算。

3. 用中断屏蔽,计算FOC的时候暂时挂起所有中断,做完了再进入排队的中断:Cortex-M3/M4内核NVIC及HAL库函数详解(5):__disable_irq和HAL_NVIC_DisableIRQ、__enable_irq和HAL_NVIC_EnableIRQ的区别_hal 没有定义 __enable_irq-CSDN博客

直接选3了!2虽然巧妙但是不如3优雅~ 

关于开ADC中断的初始化顺序,用 ADC_Init->TIM1_Init->ADC_Inject->TIM_IT->PWM 就是正常的。还有一个注意点,需要在ADC的回调用再次开启ADC的中断。

  • 32
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[3\]的代码,可以看出HAL_GetTick()函数是用来获取系统时钟的函数。它通过返回变量uwTick的值来表示经过的时间。在HAL_Delay()函数,会调用HAL_GetTick()函数来获取当前的系统时钟,并通过比较当前时钟与起始时钟的差值来实现延时功能。根据引用\[2\]的代码,可以看出在延时期间,会不断地调用HAL_GetTick()函数来判断是否达到延时的时间。如果HAL_GetTick()函数卡死,可能是由于uwTick变量没有被正确更新导致的。这可能是由于uwTick变量没有被正确地增加,或者在增加uwTick变量时发生了错误。为了解决这个问题,可以检查HAL_IncTick()函数的实现,确保uwTick变量被正确地增加。另外,还可以检查系统时钟的配置是否正确,以及时钟中断是否正常工作。 #### 引用[.reference_title] - *1* [STM32+Cube MX使用MPU6050 DMP时,在外部中断调用read_dmp函数发生系统卡死问题的根本原因分析](https://blog.csdn.net/phmatthaus/article/details/120749696)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [关于STM32的HAL库超时函数的tick溢出后产生的问题的思考及求证](https://blog.csdn.net/eibo51/article/details/53615060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [用STM32开发小记及排坑小记](https://blog.csdn.net/qq515973568/article/details/122208017)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值