
www.z-linear.comD223固件大量使用HAL库回调函数——HAL_TIM_PeriodElapsedCallback、HAL_QSPI_RxCpltCallback、HAL_SPI_TxCpltCallback……这些回调是怎么被调用的HAL库内部做了什么本文追踪从硬件中断到用户回调的完整链路。一、HAL库回调设计模式1.1 为什么用回调HAL库采用回调设计模式将硬件事件通知给用户代码传统方式寄存器: 中断 → 用户ISR → 直接处理硬件 HAL方式回调: 中断 → HAL_IRQHandler → HAL状态机 → 用户回调函数方式优势劣势直接寄存器快代码复杂、移植性差HAL回调统一接口、可移植有额外开销1.2 HAL回调的分类回调类型触发条件典型函数周期完成定时器溢出HAL_TIM_PeriodElapsedCallback传输完成DMA/发送完成HAL_QSPI_RxCpltCallback半传输DMA半完成HAL_TIM_HalfCpltCallback错误传输错误HAL_QSPI_ErrorCallback状态变化GPIO电平变化HAL_GPIO_EXTI_Callback二、HAL库中断处理链2.1 完整调用链以TIM13定时器中断为例① 硬件中断 TIM8_UP_TIM13_IRQHandler() ↓ ② HAL中断入口 HAL_TIM_IRQHandler(htim13) ↓ ③ 检测中断标志 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE)) ↓ ④ 清除中断标志 __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE) ↓ ⑤ 调用HAL内部回调 HAL_TIM_PeriodElapsedCallback(htim) ↓ ⑥ 用户重写的回调 if(htim htim13) { qspiAdcTimTask(); // D223用户代码 }2.2 HAL_TIM_IRQHandler源码分析// HAL库内部函数简化voidHAL_TIM_IRQHandler(TIM_HandleTypeDef*htim){// 检查更新中断标志if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_UPDATE)!RESET){if(__HAL_TIM_GET_IT_SOURCE(htim,TIM_IT_UPDATE)!RESET){__HAL_TIM_CLEAR_IT(htim,TIM_IT_UPDATE);// 清除标志HAL_TIM_PeriodElapsedCallback(htim);// 调用回调}}// 检查捕获/比较中断...if(__HAL_TIM_GET_FLAG(htim,TIM_FLAG_CC1)!RESET){if(__HAL_TIM_GET_IT_SOURCE(htim,TIM_IT_CC1)!RESET){__HAL_TIM_CLEAR_IT(htim,TIM_IT_CC1);HAL_TIM_OC_DelayElapsedCallback(htim);HAL_TIM_PWM_PulseFinishedCallback(htim);}}}HAL库在调用回调前自动清除中断标志用户无需手动清除。三、D223使用的HAL回调3.1 定时器周期完成回调// 用户重写的回调函数voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htimhtim12){// TIM12: 1ms系统滴答dio_tim_task();// DIO刷新PWM加减速}elseif(htimhtim13){// TIM13: ADC采样定时qspiAdcTimTask();// QSPI DMA读取FPGA FIFO}elseif(htimhtim14){// TIM14: DAC输出定时ddsDacTimTask();// DDS查表DAC写入}}一个回调函数处理多个定时器通过htim实例指针区分。3.2 QSPI DMA接收完成回调// QSPI DMA传输完成voidHAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef*hqspi){// DMA已完成66字节FPGA FIFO数据读取// 数据已到达_qspiAdcBuf_adcFunctionParam.qspiDmaComplete1;// 通知主线程// 在中断中不能做耗时操作// 仅设置标志由主线程处理数据}3.3 QSPI错误回调voidHAL_QSPI_ErrorCallback(QSPI_HandleTypeDef*hqspi){// QSPI通信错误时序错误/超时等// 记录错误并尝试恢复_adcFunctionParam.qspiErrorCount;// 重新初始化QSPIHAL_QSPI_DeInit(hqspi);HAL_QSPI_Init(hqspi);}3.4 SPI传输完成回调// SPI1FPGA寄存器传输完成voidHAL_SPI_TxCpltCallback(SPI_HandleTypeDef*hspi){if(hspihspi1){fpga_spi_tx_busy0;// 清除写忙标志HAL_GPIO_WritePin(FPGA_SPI_CS_GPIO_Port,FPGA_SPI_CS_Pin,1);// CS拉高}}// SPI1接收完成voidHAL_SPI_RxCpltCallback(SPI_HandleTypeDef*hspi){if(hspihspi1){fpga_spi_rx_busy0;// 清除读忙标志HAL_GPIO_WritePin(FPGA_SPI_CS_GPIO_Port,FPGA_SPI_CS_Pin,1);// CS拉高}}3.5 USB CDC接收回调// USB CDC数据接收完成staticint8_tCDC_Receive_FS(uint8_t*Buf,uint32_t*Len){// 数据已到达Buf长度为*Lenmemcpy(usbRxBuf,Buf,*Len);usbMsg_rxLen*Len;usbMsg_rxComplete1;// 通知消息处理线程// 重新启动接收USBD_CDC_SetRxBuffer(hUsbDeviceFS,Buf);USBD_CDC_ReceivePacket(hUsbDeviceFS);returnUSBD_OK;}四、__weak回调机制4.1 weak关键字HAL库使用__weak关键字定义回调函数。用户可以重写同名函数覆盖weak版本。// HAL库中的weak定义stm32h7xx_hal_tim.c__weakvoidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){// 空实现什么都不做UNUSED(htim);}// 用户代码中的重写非weakvoidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){// 实际处理逻辑if(htimhtim13)qspiAdcTimTask();}__weak函数的优先级低于非__weak函数。链接器优先使用用户的实现如果用户没有实现则使用HAL库的空实现。4.2 回调注册某些HAL外设支持动态注册回调// 方式1重写__weak函数D223使用voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){...}// 方式2动态注册HAL新版本支持HAL_TIM_RegisterCallback(htim13,HAL_TIM_PERIOD_ELAPSED_CB_ID,myCallback);D223使用方式1重写__weak函数因为更直观且兼容性好。五、HAL状态机5.1 外设状态枚举typedefenum{HAL_STATE_RESET0x00,// 未初始化HAL_STATE_READY0x01,// 就绪HAL_STATE_BUSY0x02,// 忙传输中HAL_STATE_TIMEOUT0x03,// 超时HAL_STATE_ERROR0x04// 错误}HAL_StatusTypeDef;5.2 QSPI状态转换HAL_QSPI_Command() → hqspi.State HAL_QSPI_STATE_BUSY → 配置命令寄存器 → hqspi.State HAL_QSPI_STATE_READY HAL_QSPI_Receive_DMA() → hqspi.State HAL_QSPI_STATE_BUSY → 启动DMA → DMA完成中断 → HAL_QSPI_RxCpltCallback() → hqspi.State HAL_QSPI_STATE_READY5.3 状态检查// 使用前检查状态if(hqspi.StateHAL_QSPI_STATE_READY){HAL_QSPI_Receive_DMA(hqspi,buffer);// 可以启动新传输}else{// 上一次传输未完成等待}六、回调执行上下文6.1 在中断上下文执行HAL回调在中断上下文中执行不能调用阻塞函数。// ❌ 错误中断回调中调用阻塞函数voidHAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef*hqspi){HAL_Delay(100);// ❌ 死循环SysTick优先级低rt_thread_delay(2);// ❌ 死锁中断不能调度线程}// ✅ 正确只设标志位voidHAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef*hqspi){_adcFunctionParam.qspiDmaComplete1;// ✅ 设置标志}6.2 中断中可安全调用的函数函数可安全调用说明设置标志位✅最安全读取寄存器✅快速操作HAL_GPIO_WritePin✅直接寄存器操作HAL_Delay❌依赖SysTick中断rt_thread_delay❌需要调度器HAL_SPI_Transmit⚠️阻塞但可短时使用memcpy✅纯内存操作七、D223回调总结回调函数触发源优先级用户处理耗时HAL_TIM_PeriodElapsedCallbackTIM126dio_tim_task()~5μsHAL_TIM_PeriodElapsedCallbackTIM135qspiAdcTimTask()~2μsHAL_TIM_PeriodElapsedCallbackTIM145ddsDacTimTask()~3μsHAL_QSPI_RxCpltCallbackDMA1 S54设标志位1μsHAL_SPI_TxCpltCallbackSPI17CS拉高清标志1μsHAL_SPI_RxCpltCallbackSPI17CS拉高清标志1μsCDC_Receive_FSUSB3设标志重启接收2μsHAL_QSPI_ErrorCallbackQSPI4重新初始化~10μs所有回调执行时间都在10μs以内确保不影响其他中断和主线程。八、回调函数调试技巧8.1 在回调中设置断点voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htimhtim13){__asmvolatile(NOP);// 设置断点在这里qspiAdcTimTask();}}8.2 使用ITM打印voidHAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef*hqspi){ITM_SendChar(D);// 通过ITM输出调试字符_adcFunctionParam.qspiDmaComplete1;}8.3 使用GPIO翻转测量时间voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htimhtim13){HAL_GPIO_WritePin(DBG_GPIO_Port,DBG_Pin,1);// 拉高qspiAdcTimTask();HAL_GPIO_WritePin(DBG_GPIO_Port,DBG_Pin,0);// 拉低// 用示波器测量高电平时间 回调执行时间}}九、HAL回调 vs LL库9.1 HAL vs LL对比维度HAL库LL库抽象层次高低代码量大小执行效率中高回调机制✅❌可移植性好一般D223使用✅—9.2 D223中的HAL和直接寄存器混用// 定时器中断中使用HALvoidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htimhtim13){qspiAdcTimTask();// 内部使用HAL_QSPI_Receive_DMA}}// PWM频率更新中使用直接寄存器voidfun_calcPwmParam(u8 ch,TIM_HandleTypeDef*_pTim){_pTim-Instance-PSC_dioParam.psc[ch];// 直接写寄存器_pTim-Instance-ARR_dioParam.arr[ch];_pTim-Instance-CCR1_dioParam.duty[ch];}在中断中需要最高效率的地方如PWM参数更新使用直接寄存器操作其他地方使用HAL函数。十、总结D223的HAL回调机制实现了设计要点实现回调机制__weak函数重写定时器回调HAL_TIM_PeriodElapsedCallbackTIM12/13/14QSPI DMA回调HAL_QSPI_RxCpltCallback设标志位SPI回调HAL_SPI_TxCpltCallback/RxCpltCallbackCS控制USB回调CDC_Receive_FS设标志重启接收错误回调HAL_QSPI_ErrorCallback重新初始化回调上下文中断上下文不阻塞回调耗时10μs所有回调HAL状态机BUSY→READY→BUSY循环混用策略HAL为主关键路径直接寄存器从硬件中断触发到HAL状态机检测标志位从清除中断标志到调用__weak回调函数HAL库的回调机制为D223提供了统一的硬件事件处理框架。用户只需重写回调函数并判断htim实例即可在统一的框架下处理所有外设事件。下篇预告《C#上位机帧解析引擎深度解析从字节流到结构体》