STM32入门基础篇(九)

(九)定时器

1.分类

  • 基本定时器:TIM6、TIM7
  • 通用定时器:TIM2-5、TIM9-TIM14
  • 高级定时器:TIM1、TIM8

1.1.通用定时器

  • STM32F4的通用定时器包含一个 16 位或 32 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC)驱动。

  • 通用定时器具有如下功能:

    • 16位/32位向上、向下、向上/向下自动装载计数器(TIMx_CNT)

      注:仅TIM2和TIM5是32位(在设置period参数时应选用u32类型的);TIM9~TIM4只支持向上(递增)计数
    • 16位可编程(可实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意数值

    • 可使用外部信号(TIMx_ETR,外部控制管脚,开发板原理图可查)控制定时器,且可实现多个定时器互连(可以用1个定时器控制另外一个定时器)的同步电路(很少使用)

    • 支持针对定位的增量(正交)编码器和霍尔传感器电路(TIM9-TIM14不支持)

    • 触发输入作为外部时钟或者按周期的电流管理( TIM9-TIM14不支持)

    • 发生如下事件时产生中断/DMA请求(TIM9-TIM14不支持DMA):

      • 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
      • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
      • 输入捕获
      • 输出比较
    • 4个独立通道(TIMx_CH1-4,TIM9-TIM14最多2个通道),用来作为:

      • 输入捕获
      • 输出比较
      • PWM生成(边缘对齐或中间对齐,其中,TIM9~TIM14不支持中间对齐)
      • 单脉冲模式输出
  • STM32F4的每个通用定时器都是完全独立的,没有互相共享的任何资源。

1.2.通用定时器结构图

在这里插入图片描述

stm32f4xx.h文件中可以找到各种总线时钟的定义。

2.通用定时器配置步骤

  • 使能定时器时钟

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);//使能TIM4时钟
    
  • 初始化定时器参数,包含自动重装值,分频系数,计数方式等,将stm32f4xx_time.c文件添加到工程组中、stm32f4xx_time.h添加文件路径

    void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
    typedef struct
    {
      uint16_t TIM_Prescaler;         //定时器预分频器
      uint16_t TIM_CounterMode;       //计数模式:向上计数、向下计数、中心对齐
      uint32_t TIM_Period;            //定时器周期:此处是uint32_t表示32位(仅对应TIM2和TIM5),其余是uint_16即16位
      uint16_t TIM_ClockDivision;     //时钟分频
      uint8_t TIM_RepetitionCounter;  //重复计数器
    } TIM_TimeBaseInitTypeDef;
    
  • 设置定时器中断类型,并使能

    void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
    
  • 设置定时器中断优先级,使能定时器中断通道

    NVIC_Init();//初始化库函数
    
  • 开启定时器

    void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
    
  • 编写定时器中断服务函数

    TIM4_IRQHandler//中断服务函数名
    
    ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//获取中断状态标志,参数分别是定时器名和中断类型
    if(TIM_GetITStatus(TIM4,TIM_IT_Update))
    	{
    		...//执行TIM4更新中断内控制
    	}
    
    void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//清除中断状态标志
    
    • 另:固件库中还有两个函数是用来读取状态标志位以及清除中断标志位,函数分别为TIM_GetFlagStatus和TIM_ClearFlag,与上面的差别在于:前者会先判断中断是否开启在确认开启后再判断/清除标志位,后者直接判断/清除标志位。

3.编程操作

注:可以先将外部中断的程序移除(exti.c和exti.h)
  1. 新建文件夹在APP内,并新建文件time.c和time.h将两个文件加入工程组、添加路径

  2. 编写time.h程序如下:

    #ifndef _time_H
    #define _time_H
    
    #include "system.h"
    
    #endif
    
  3. 编写time.c程序如下:

    #include "time.h"
    
    void TIM4_Init(u16 pre,u16 psc)//初始化函数:第一个u16类型参数的原因是TIM4定时器是16位的
    {
        NVIC_InitTypeDef NVIC_InitStructure;
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
        
        TIM_TimeBaseInitStructure.TIM_Period=pre;
        TIM_TimeBaseInitStructure.TIM_Prescaler=psc;//通过配置该项,将时钟输入的时钟源经过预分频成为的时钟
        TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_1;//配置该项一般在滤波器使用时,在此时是不用的则设置为1分频
        TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
        //TIM_TimeBaseInitStructure.TIM_RepetitionCounter通用定时器不用配置此项,此项用于高级定时器
        
    	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
        
        TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//第二个参数指的是更新中断
        TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//在程序伊始无法判断定时中断标志位情况时,先将其清除
        
        NVIC_InitStructure.NVIC_IQRChannel=TIM4_IQRn;//配置中断源,此处要进行修改成定时器4
        NVIC_InitStructure.NVIC_IQRChannelPreemptionPriority=2;//抢占优先级
        NVIC_InitStructure.NVIC_IQRChannelSubPriority=3;//响应优先级
        NVIC_InitStructure.NVIC_IQRChannelCmd=ENABLE;//使能中断
        NVIC_Init(&NVIC_InitStructure);//初始化结构体
        
        TIM_Cmd(TIM4,ENABLE);
    }
    
    void TIM4_IRQHandler(void)//中断服务函数
    {
        if(TIM_GetITStatus(TIM4,TIM_IT_Update))
        {
            led2=!led2;
        }
        TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
    }
    
  4. 完成函数的声明:

    #ifndef _time_H
    #define _time_H
    
    #include "system.h"
    
    void TIM4_Init(u16 pre,u16 psc);
    void TIM4_IRQHandler(void);
    
    #endif
    
  5. 根据需要完成主函数的编写:

    #include "system.h"
    #include "zs_pro_standard.h"
    #include "SysTick.h"
    #include "time.h"
    
    
    int main()
    {
    	u8 i;//不知名原因,此行内容不能放到下一行后面
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//进行优先级位的分组
        IpO_Init();
        
        TIM4_Init(5000-1,8400-1);//定时500ms
        //计数器从5000开始计数,计数至0时刚好为5001次,因此要减去一次,8400同理减一
        //84MHz在预分频系数为8400状态下的频率变为10KHz,即计数一次为0.1ms
        
        while(1)
        {
            i++;
            if(i%20==0)
            {
                led1=!led1;
            }
            delay_ms(10);
        }
    }
    

4.时间轮询

  • 以一定时间间隔轮番查询是否到达指定时间并对任务进行变更

  • 步骤如下:

    1. 在单片机初始化任务完成以后,再初始化基频定时器;
    2. 设置用于计数的参数,用于记录进入中断服务函数的次数,达到指定次数时将相应任务的标志位更改;
    3. main函数中使用 switchif 对标志位进行判断,实现任务的内容或数量的变更。
  • 优点:

    • 能够实现伪多线程

    • 定时相对准确

    • 相较于使用 delay 函数实现的任务切换,能够及时进行任务变更

      假设 GPIOA9 管脚连接光电开关

      • 使用 delay 函数时:

        当光电开关检测到物体、引起管脚电平变化时,应当及时触发下一步的任务变更。但是由于使用了 delay 函数,系统停滞在其内部的 while 循环中无法跳出,任务变更这一事件就无法得到及时触发。

      • 使用时间轮询时:

        当光电开关检测到物体、引起管脚电平变化时,在基频定时器的中断服务函数中会将下一步任务的标志位更改。当跳出中断后,main函数就会立即对任务是否变更进行判断。

  • 值得注意的是:

    • 基频的大小能否满足响应速度的需要
      • 基频太小时,任务切换快,但是由于频繁开启中断会导致处理速度变慢
      • 基频太大时,任务且缓慢,如果有电平变化时间小于基频对应的时间,可能会检测不到
    • 中断服务函数中不宜使用大量计算或处理程序,避免中断占用时间过长
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值