PY32 定时器中断回调不执行的解决办法
一,发现问题
我在调试 PY32 的定时器中断时,按 HAL 库的流程配好了定时器,也写了回调函数,但全速运行后回调就是不执行。
voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM14){// 这里的代码从来不执行}}打断点发现 HAL 层的回调根本没被调用。
二,排查过程
我先是检查了定时器分频、重装值、中断优先级,都没问题。
然后去查了一下,发现根因在 startup 启动文件里:PY32 的 startup 文件虽然标了TIM14_IRQHandler这个标签,但它是一个weak 弱别名,默认指向一个空函数。中断发生后 CPU 跳到了空函数里,根本没进到 HAL 库。
三,解决方法
手动实现TIM14_IRQHandler,把中断重定向到HAL_TIM_IRQHandler。下面是一个完整的可运行例程:
#include"py32f0xx_hal.h"TIM_HandleTypeDef htim14;volatileuint32_ttimer_irq_count=0;staticvoidAPP_SystemClockConfig(void){RCC_OscInitTypeDef RCC_OscInitStruct={0};RCC_ClkInitTypeDef RCC_ClkInitStruct={0};RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIState=RCC_HSI_ON;RCC_OscInitStruct.HSIDiv=RCC_HSI_DIV1;RCC_OscInitStruct.HSICalibrationValue=RCC_HSICALIBRATION_24MHz;HAL_RCC_OscConfig(&RCC_OscInitStruct);RCC_ClkInitStruct.ClockType=RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1;RCC_ClkInitStruct.SYSCLKSource=RCC_SYSCLKSOURCE_HSI;RCC_ClkInitStruct.AHBCLKDivider=RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider=RCC_HCLK_DIV1;HAL_RCC_ClockConfig(&RCC_ClkInitStruct,FLASH_LATENCY_0);}staticvoidAPP_TimerInit(void){__HAL_RCC_TIM14_CLK_ENABLE();htim14.Instance=TIM14;htim14.Init.Prescaler=23999;// 24MHz/(23999+1) = 1KHzhtim14.Init.CounterMode=TIM_COUNTERMODE_UP;htim14.Init.Period=999;// 1KHz/1000 = 1Hz 中断htim14.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;htim14.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE;HAL_TIM_Base_Init(&htim14);HAL_NVIC_SetPriority(TIM14_IRQn,1,0);HAL_NVIC_EnableIRQ(TIM14_IRQn);HAL_TIM_Base_Start_IT(&htim14);}/* 关键:手动实现 IRQHandler,调用 HAL 库的处理函数 */voidTIM14_IRQHandler(void){HAL_TIM_IRQHandler(&htim14);}/* 定时器中断回调 */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM14){timer_irq_count++;}}intmain(void){HAL_Init();APP_SystemClockConfig();APP_TimerInit();while(1){// 主循环,timer_irq_count 每秒自增一次}}把上面这段代码复制到一个新工程,添加 HAL 库后编译下载,定时器中断就能正常工作了。
四,补充说明:为什么 HAL 要用回调
中断发生后,CPU 直接跳到向量表里的函数。但 HAL 库不知道用户想在这个中断里做什么事,所以它设计了两层 weak 定义:
TIM14_IRQHandler (weak) ← startup 给的默认空函数 ↓ 你实现了就覆盖 你写的 TIM14_IRQHandler → HAL_TIM_IRQHandler() ← HAL 帮你清标志 → HAL_TIM_PeriodElapsedCallback (weak) ← 你实现的回调HAL 负责通用操作(判断哪个中断源、清标志),用户只需实现回调。互不干扰。
也可以不用回调,直接在中断函数里处理所有事,结果一样:
voidTIM14_IRQHandler(void){// 在这里判断标志、清标志、做事,不经过 HAL}两种写法选一种即可,没有强制。
五,总结
PY32 的 startup 文件只给了中断入口标签,默认是弱定义。想让 HAL 库的中断回调生效,必须自己实现 IRQHandler 并调用对应的 HAL 处理函数。
同理,串口中断、外部中断 etc. 都需要手动重定向,例如:
voidUSART2_IRQHandler(void){HAL_UART_IRQHandler(&huart2);}voidEXTI0_1_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);}这个步骤容易忽略,希望能帮到遇到同样问题的人。
如果有纰漏之处,欢迎指正。
