对STM32 HAL库的一些思考(二)__weak关键字和systick

本文探讨了STM32 HAL库中__weak关键字的作用,它允许函数重定义并实现了类似C++的函数重载特性。同时,详细介绍了Systick定时器作为内核的心跳定时器,如何为系统提供时基信号,并在HAL库中如何应用。通过理解这两个概念,可以更好地自定义延时函数,同时避免直接修改库文件带来的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

__weak关键字

有时候我们阅读HAL源码的时候会发现,很多函数前会有一个__weak的前缀,一开始我对这个前缀很好奇,想知道这到底是什么意思。在IAR for ARM中使用“go to defination”查看该关键字的时候会提示__weak defined on the command line,即在命令行中定义,得,这又回到原点了……

不过一次偶然的机会,在查找C语言相关资料时,我看到它的真实含义,该关键字用于函数前,编译器在链接函数时会优先链接没有该关键字前缀的函数,即变相的,而且是不完全的实现了C++的函数重载特性,示例:

//HAL库微秒延时函数,有__weak关键字即代表可以重载该函数
__weak HAL_Delay(__IO uint32_t delay);
//用户函数
HAL_Delay(__IO uint32_t delay);

以上两个函数,编译器会优先链接下面的函数,即为我们重定义延时函数提供了较大的便利性。


Systick定时器

了解ARM的人对Systick定时器一定不陌生,这是内核自带的定时器,不过我更喜欢称它为心跳定时器,一般在裸机开发时,由它提供整个内核的时基信号,当然也可以替换为外围的定时器,这一点可以在STM32CubeMX中得到验证,此处不赘述。

不过为什么要把systick和__weak关键字扯在一起呢?这当然是有原因的。

如果有阅读过HAL库中关于cortex部分的源码,你会发现关于心跳定时器的每一个函数前都有__weak关键字,以STM32F407为例,这部分源码在stm32f4xx_hal.h/.c和stm32f4xx_hal_cortex.h/.c中,之前网上有很多修改Tick的初始化以达到使HAL_Delay()函数可以进行微秒级延时的例子,这里不多说这种方法,只是阐述一下HAL_Delay()的延时原理。

  1. Tick的默认初始化

    __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
    {
      /*Configure the SysTick to have interrupt in 1ms time basis*/
      HAL_SYSTICK_Config(SystemCoreClock/1000U);
    
      /*Configure the SysTick IRQ priority */
      HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
    
      /* Return function status */
      return HAL_OK;
    }

    经过此函数,心跳定时器被初始化为1ms的时基单元,即每毫秒进入一次Systick中断

  2. Systick中断及其回调函数

        /**
         * stm32f4_it.c
        **/
    
    void SysTick_Handler(void)
    {
          /* USER CODE BEGIN SysTick_IRQn 0 */
    
          /* USER CODE END SysTick_IRQn 0 */
          HAL_IncTick();
          HAL_SYSTICK_IRQHandler();
          /* USER CODE BEGIN SysTick_IRQn 1 */
    
          /* USER CODE END SysTick_IRQn 1 */
    }
    
    /**
     * stm32f4xx_hal.c
     * uwTick是一个全局变量,在该文件中定义
    **/
    __weak void HAL_IncTick(void)
    {
        uwTick++;
    }

    即每毫秒uwTick加一,用于计算Systick产生的节拍数

  3. 获取当前节拍

    /**
     * stm32f4xx_hal.c
     * uwTick是一个全局变量,在该文件中定义
    **/
    
    __weak uint32_t HAL_GetTick(void)
    {
        return uwTick;
    }

    返回当前节拍,如果内核正常运行,则uwTick的值即为已经经过的ms数(不考虑精确度)

  4. 延时函数

    /**
     * stm32f4xx_hal.c
     * uwTick是一个全局变量,在该文件中定义
    **/
    
    __weak void HAL_Delay(__IO uint32_t Delay)
    {
        uint32_t tickstart = HAL_GetTick();
        uint32_t wait = Delay;
    
        /* Add a period to guarantee minimum wait */
        if (wait < HAL_MAX_DELAY)
        {
            wait++;
        }
    
        while((HAL_GetTick() - tickstart) < wait)
        {
        }
    }

    我想这个函数不需要多解释,就是数节拍以确定延时。


既然了解了HAL的延时函数是如何延时的,则我们完全可以重写这些函数以实现花样延时,但是不建议直接修改库文件,有__weak在,就可以在其他文件中书写自己的函数而不是破坏库函数原有结构了。另外,修改了库函数,在使用CubeMX重新生成的时候,所做的修改会消失,这一点要慎重。

<think>嗯,用户这次想了解STM32 HAL中定时器相关函的使用指南。从问题描述看,用户应该是有一定STM32开发基础的程序员,正在使用HAL进行定时器开发。这类问题通常出现在嵌入式开发的实际项目中,用户可能需要配置定时器实现PWM输出、输入捕获或计时等功能。 用户之前提到了HAL_Init(),但这次明确聚焦在定时器功能上。HAL的定时器模块确实比较复杂,涉及基本定时器、通用定时器、高级定时器等不同类型,每种都有特定的函集。我需要把最关键的核心函配置流程讲清楚。 首先想到的是定时器初始化的HAL_TIM_Base_Init(),这是所有定时器功能的基础。然后是六种工作模式对应的启动函,比如PWM模式要用HAL_TIM_PWM_Start()。用户可能容易混淆不同模式的启动函,这点需要特别说明。 中断处理回调函机制也很重要。HAL采用统一的中断处理框架,用户只需要重写__weak修饰的回调函。比如PWM周期结束会触发HAL_TIM_PeriodElapsedCallback(),这个设计模式需要解释清楚。 在提供示例代码时,选择PWM输出作为典型场景比较合适,因为这是定时器最常用的功能之一。代码要展示从TIM初始化到GPIO配置的完整流程,特别是TIM_OC_InitTypeDef这个结构体的配置细节,用户经常在这里出错。 考虑到用户可能遇到的坑,需要提醒注意点:时钟使能容易被遗漏,ARR/PSC寄存器计算错误会导致频率不对,还有PWM占空比的计算方法。这些实际开发中的经验点比手册上的参列表更有价值。 最后生成的问题要覆盖进阶需求,比如如何用定时器做编码器接口或者输入捕获,这些都是用户后续可能问到的。DMA配合定时器传输据也是个实用场景,值得单独提出来。 对了,引用标注要自然融入正文。HAL_Init()的引用放在初始化部分很合适,但要注意用户当前更关注定时器而非系统初始化。所有的函寄存器名必须用$...$格式,比如$ARR$寄存器,保持技术文档的规范性。</think>以下是STM32 HAL定时器功能函的使用指南,涵盖核心函、配置流程及关键注意事项: --- ### **一、定时器核心功能函** #### 1. **初始化与启动** | 函 | 作用 | |--------------------------|----------------------------------------------------------------------| | `HAL_TIM_Base_Init()` | 初始化定时器时基(配置预分频器$PSC$、自动重载寄存器$ARR$等)[^1] | | `HAL_TIM_Base_Start()` | 启动定时器计(无中断/DMA) | | `HAL_TIM_Base_Start_IT()`| 启动定时器并开启更新中断 | | `HAL_TIM_Base_Start_DMA()`| 启动定时器并开启DMA传输 | #### 2. **PWM模式** ```c HAL_TIM_PWM_Init(&htim); // 初始化PWM HAL_TIM_PWM_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1); // 配置通道 HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1); // 启动PWM输出 ``` **关键结构体**: ```c TIM_OC_InitTypeDef sConfig = { .OCMode = TIM_OCMODE_PWM1, // PWM模式 .Pulse = 500, // 占空比脉冲值(CCR寄存器值) .OCPolarity = TIM_OCPOLARITY_HIGH, // 输出极性 }; ``` #### 3. **输入捕获** ```c HAL_TIM_IC_Init(&htim); HAL_TIM_IC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim, TIM_CHANNEL_1); // 启动捕获并开启中断 ``` **中断回调函**: ```c void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 读取捕获值 } } ``` #### 4. **编码器模式** ```c HAL_TIM_Encoder_Init(&htim, &sConfig); // 初始化编码器 HAL_TIM_Encoder_Start(&htim, TIM_CHANNEL_ALL); // 启动编码器 ``` **配置结构体**: ```c TIM_Encoder_InitTypeDef sConfig = { .EncoderMode = TIM_ENCODERMODE_TI12, // 编码器模式 .IC1Filter = 0x0F, // 输入滤波器 .IC1Polarity = TIM_ICPOLARITY_RISING // 捕获极性 }; ``` --- ### **、配置流程示例:PWM输出** #### 步骤分解 1. **定时器时钟使能** ```c __HAL_RCC_TIM2_CLK_ENABLE(); // 使能TIM2时钟 ``` 2. **配置时基** ```c TIM_HandleTypeDef htim; htim.Instance = TIM2; htim.Init.Prescaler = 79; // 预分频器$PSC$(80MHz/80=1MHz) htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = 999; // $ARR$值(1MHz/1000=1kHz PWM) HAL_TIM_Base_Init(&htim); ``` 3. **配置PWM通道** ```c TIM_OC_InitTypeDef sConfig = {0}; sConfig.OCMode = TIM_OCMODE_PWM1; sConfig.Pulse = 500; // 50%占空比($CCR=500$) sConfig.OCPolarity = TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1); ``` 4. **启动PWM** ```c HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1); ``` --- ### **三、关键注意事项** 1. **时钟配置** 确保定时器时钟源已使能(通过`__HAL_RCC_TIMx_CLK_ENABLE()`),且APB时钟分频正确(影响定时器实际频率)。 2. **中断与DMA** - 中断优先级配置:`HAL_NVIC_SetPriority(TIMx_IRQn, 0, 0)` - DMA流配置:使用`HAL_TIM_Base_Start_DMA()`时需提前绑定DMA通道。 3. **寄存器级操作** 直接访问寄存器可提升效率(如修改占空比): ```c __HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, new_pulse); // 快速更新CCR ``` 4. **超时处理** 调用`HAL_TIM_xxx`函后检查返回值,例如: ```c if (HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); // 启动失败处理 } ``` --- ### **四、调试技巧** - **使用CubeMX**:图形化配置定时器参(自动生成初始化代码)。 - **逻辑分析仪**:验证PWM频率/占空比、捕获信号时序。 - **HAL状态查询**:`__HAL_TIM_GET_FLAG(&htim, TIM_FLAG_UPDATE)`检查中断标志。 > [^1]: HAL初始化时需调用`HAL_Init()`配置系统时钟SysTick,这是定时器工作的基础。 --- ### 相关问题 1. **如何用HAL实现定时器中断精确计时1ms?** 2. **PWM模式下如何动态调整占空比?** 3. **编码器模式如何读取电机转速?** 4. **输入捕获测量高频信号时要注意什么?** 5. **如何用定时器触发ADC采样?**
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值