STM32 定时器的几种基本使用


title: STM32——外设Timer定时器
date: 2020-05-20 14:43:52
tags:
categories: STM32学习记录


对定时器的基本认识

先来看看这种MCU有多少定时器:

STM32 定时器的几种基本使用_第1张图片

定时器分为3类:

  1. 基本定时器的功能最少,只能充当基本的时基,甚至都没有外部引脚。
  2. 通用定时器拥有基本定时器的全部功能,同时有输入捕获模式,用以接收外部的PWM,脉冲之类的信息,也有
  3. 高级定时器又有通用定时器的全部功能,又有互补输出模式,功能最为强大

先具体看一下夹在中间的通用定时器的官方文档中的描述:

STM32 定时器的几种基本使用_第2张图片

为什么需要定时器,并且需要这么多定时器呢?这是因为STM32的处理器是一种单线程的模式,这这时如果没有一个专门的外设,那么在软件定时期间就无法处理其他的工作。于是ST就提供了这些可以独立工作的定时器来完成需要的定时工作。

定时器的心脏:时基

1、内部时钟(CK_INT)

2、外部时钟模式 1:外部输入脚(TIx)

3、外部时钟模式 2:外部触发输入(ETR)

4、内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。 这些时钟,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置。

STM32 定时器的几种基本使用_第3张图片

在这里呢,最基本的定时功能就是功能1,如上图,基本定时器和通用定时器是挂载在APB1低速总线的,和PCLK共用来自APB1预分频器的时钟源,这个总线的最大频率是36M,但是由于预分频器分频倍数是1,所以在这里就由系统自动给双倍频率了,因此基本定时器和通用定时器的时钟频率都是72MHz的。

实践1:定时器溢出中断

原理

这个功能就和传统8051单片机的定时器功能一样,计数,溢出,触发中断,简单明了,多了个自动重装载功能,应该是最简单的定时器功能了,甚至基本定时器都能够完成,我就从这里开始学习定时器吧。

和GPIO,串口,外部中断一样,创建并添加src文件和inc文件:

TIMER.h

#ifndef _TIMER_H
#define _TIMER_H

extern TIM_HandleTypeDef TIM3_Handler;      //定时器句柄

void TIM3_Init(u16 arr,u16 psc);
#endif

TIMER.c

首先是初始化程序,一切是那么熟悉,从GPIO起,我们都使用HAL库提供的初始化结构体来初始化外设。

#include "TIMER.h"
#include "LED.h"

TIM_HandleTypeDef TIM3_Handler;      //定时器句柄 

//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM3_Init(uint16_t arr,uint16_t psc)		//arr:自动重装值	psc:时钟预分频数
{  
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
    HAL_TIM_Base_Init(&TIM3_Handler);			//时基初始化
    
    HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE   
}

追究底层原理的话,这些给结构体成员赋值的过程中,主要就是在操作CR1,CR2控制寄存器,完成定时器最基本的设置基础计时频率的过程,经过初始化函数上,就完成了这些配置。

CounterMode:TIM_COUNTERMODE_UP TIM_COUNTERMODE_DOWN下

Channel:用来设置活跃通道。每个定时器最多有四个通道可 以用来做输出比较,输入捕获等功能之用。这里的 Channel 就是用来设置活跃通道的,取值范 围为:HAL_TIM_ACTIVE_CHANNEL_1~ HAL_TIM_ACTIVE_CHANNEL_4。

这里呢,和串口类似,在时基初始化函数中,会调用一个__WEAK弱定义过的空函数:

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
	{
		__HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
		HAL_NVIC_SetPriority(TIM3_IRQn,1,3);    //设置中断优先级,抢占优先级1,子优先级3
		HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断   
	}
}

这样,初始化才正式完成,然后是中断向量表中的中断服务函数:

void TIM3_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM3_Handler);
}

又是熟悉的感觉,直接调用HAL库的处理函数,我们按照惯例,进去看看:

STM32 定时器的几种基本使用_第4张图片

这个函数居然有300多行,不过还好,机智的我把它折叠起来了,如果忽略具体实现细节,这个其实很清晰了,这个函数的作用是,通过判断定时器的寄存器,判断触发中断的到底是什么事件,是捕获比较1234,还是定时器更新事件,还是输入捕获事件,然后调用相应的回调函数。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim==(&TIM3_Handler))
    {
		HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
		HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
    }
}

在这里我写的是一个闪灯程序(我在前面包含了LED.h头文件)

在主程序中初始化:

#include "main.h"
#include "stm32f1xx.h"		//芯片选型的功能,里面包含了stm32f1xx_hal.c

#include "LED.h"
#include "TIMER.h"


int main(void)
{
	HAL_Init();
	SystemClock_Config();
	LED_Init();
	TIM3_Init(9999, 7199);
	while(1)
	{
	}
}

可以看到初始化的两个重要参数:

void TIM3_Init(uint16_t arr,uint16_t psc)		//arr:自动重装值	psc:时钟预分频数
  • 自动重载值:指的是计数多少次自动重装,16位寄存器储存这个值,范围是0 - 65535,这个是向上计数,就是0到arr就会触发重装中断事件
  • 时钟预分频数:指的是相对于时钟基础频率(这里是72MHz),的分频数,这个也很强大,也是16位寄存器储存这个值,范围仍然是0 - 65535。

专门有2个寄存器:自动重载寄存器 预分频寄存器,看看官方的公式:

STM32 定时器的几种基本使用_第5张图片

f_CK_PSC就是时钟频率,这里是2倍频后的APB1时钟,72MHz。

再来看看这个初始化函数:

TIM3_Init(9999, 7199);

72e-6 / (7199+1) = 1e-4 (s) 就是0.1ms的计数间隔。计数范围是0 - 9999,一共1e4一万个计数次数,相当于1s一次,触发中断,自动装填,重新运行。

实践2:定时器输出PWM波

### 原理

先来看看原理,怎么输出占空比不同的,稳定频率的PWM波(节选自正点原子):

STM32 定时器的几种基本使用_第6张图片

假定定时器工作在向上计数 PWM 模式,且当 CNT=CCRx 时输出 1。那么就可以得到如上的 PWM 示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候, IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。 改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。

前面也看到了,只有基本定时器是没有外部引脚的,不能作为输出,其他的定时器都可以输出PWM波,这里以通用定时器作为工具,操作单片机输出PWM波。

除了实践1中用到的寄存器,还有3种和捕获/比较有关的寄存器:2个配置用的模式寄存器TIMx_CCMR1/2

STM32 定时器的几种基本使用_第7张图片

4个工作时用的状态寄存器TIMx_CCR1/2/3/4,其实这里只用到一个,因为通用定时器有4个通道,一个通道对应一个这种寄存器,列出来一个看看

STM32 定时器的几种基本使用_第8张图片

1个使能寄存器TIMx_CCER

STM32 定时器的几种基本使用_第9张图片

可以看到每两位控制一个通道,然后隔一个没用的保留位。

然后前面我们了解到除了基本定时器,其他的定时器都是有对应引脚的,这就是重映射复用功能:

STM32 定时器的几种基本使用_第10张图片

PWM.h

#ifndef __PWM_H
#define __PWM_H

#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"

extern TIM_HandleTypeDef TIM3_Handler;      //定时器句柄 

void TIM3_PWM_Init(uint16_t arr,uint16_t psc);
void TIM_SetTIM3Compare2(uint32_t compare);

#endif

PWM.c

#include "PWM.h"
#include "LED.h"

TIM_HandleTypeDef 	TIM3_Handler;	  	//定时器句柄 
TIM_OC_InitTypeDef 	TIM3_CH2Handler;	//定时器3通道2句柄

void TIM3_PWM_Init(uint16_t arr,uint16_t psc)
{  
	TIM3_Handler.Instance=TIM3;							//定时器3
	TIM3_Handler.Init.Prescaler=psc;					//定时器分频
	TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;	//向上计数模式
	TIM3_Handler.Init.Period=arr;		  				//自动重装载值
	TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
	
	HAL_TIM_PWM_Init(&TIM3_Handler);					//初始化PWM
	
	TIM3_CH2Handler.OCMode=TIM_OCMODE_PWM1;				//模式选择PWM1
	TIM3_CH2Handler.Pulse=arr/100;						//设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
	TIM3_CH2Handler.OCPolarity=TIM_OCPOLARITY_LOW;		//输出比较极性为低
	
	HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH2Handler,TIM_CHANNEL_2);	//配置TIM3通道2
	
	HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_2);		//开启PWM通道2
	 	   
}

实践3:PWM输出用于驱动直流有刷减速电机

这一篇属于PWM的应用方面:节选自我的另一篇博客

MOTOR.h

#ifndef __MOTOR_H
#define __MOTOR_H
#include 			//包含引脚转换的宏函数,便于编写代码

#define PWMA   TIM1->CCR1  //用于控制A电机的PWM占空比调节
#define AIN2   PBout(15)
#define AIN1   PBout(14)

#define PWMB   TIM1->CCR4  //用于控制B电机的PWM占空比调节
#define BIN1   PBout(13)
#define BIN2   PBout(12)

void MiniBalance_PWM_Init(u16 arr,u16 psc);
void MiniBalance_Motor_Init(void);
#endif

这里干了几件事:

  • 电机控制引脚宏定义

  • PWM占空比改变宏定义

  • 两个初始化函数宏定义

MOTOR.c

首先是对4个控制引脚的初始化,高速下拉推挽输出:

#include "MOTOR.h"

TIM_HandleTypeDef 	TIM1_Handler = {0};	  	//定时器1句柄 
TIM_OC_InitTypeDef 	TIM1_CH14Handler = {0};	//定时器1通道14句柄


void MiniBalance_Motor_Init(void)		//完成4个控制引脚的初始化
{
	GPIO_InitTypeDef GPIO_Initure = {0};	//声明初始化结构体
	__HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟

	GPIO_Initure.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;	//端口配置
	GPIO_Initure.Pull = GPIO_NOPULL;
	GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;	  						//推挽输出
	GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;					 //50M
	
	HAL_GPIO_Init(GPIOB, &GPIO_Initure);				  //根据设定参数初始化GPIOB 
}

然后是对PWM外设的初始化,这里用定时器1的1、4通道,映射到PA8和PA11两个引脚用来控制两个电机的转速:

//TIM1 PWM初始化 
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void MiniBalance_PWM_Init(u16 arr,u16 psc)		//完成两个PWM输出引脚的初始化
{
	TIM1_Handler.Instance=TIM1;							//定时器1
	TIM1_Handler.Init.Prescaler=psc;					//定时器分频
	TIM1_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;	//向上计数模式
	TIM1_Handler.Init.Period=arr;		 				//自动重装载值
	TIM1_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
	HAL_TIM_PWM_Init(&TIM1_Handler);	  			 //初始化PWM
	
	TIM1_CH14Handler.OCMode=TIM_OCMODE_PWM1;		 	//模式选择PWM1
	TIM1_CH14Handler.Pulse= 0;						//设置比较值,初始占空比设为0
	TIM1_CH14Handler.OCPolarity=TIM_OCPOLARITY_HIGH; 	//输出极性 高电平有效 
	HAL_TIM_PWM_ConfigChannel(&TIM1_Handler,&TIM1_CH14Handler,TIM_CHANNEL_1);//初始化TIM1,PWM通道1、4
	HAL_TIM_PWM_ConfigChannel(&TIM1_Handler,&TIM1_CH14Handler,TIM_CHANNEL_4);//初始化TIM1,PWM通道1、4
	
	HAL_TIM_PWM_Start(&TIM1_Handler,TIM_CHANNEL_1);	//开启PWM通道1、4
	HAL_TIM_PWM_Start(&TIM1_Handler,TIM_CHANNEL_4);	//开启PWM通道1、4
}

在HAL_TIM_PWM_Init()中会调用端口初始化MSP函数,需要用户重定义覆盖HAL库的若定义函数:

//定时器底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	GPIO_InitTypeDef GPIO_Initure;
	
    if(htim->Instance==TIM1)
	{
		__HAL_RCC_TIM1_CLK_ENABLE();			//使能TIM1时钟
		__HAL_AFIO_REMAP_TIM1_PARTIAL();		//TIM1通道引脚部分重映射使能
		__HAL_RCC_GPIOA_CLK_ENABLE();			//开启GPIOA时钟
		
		GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_11;           	//PA8 PA11
		GPIO_Initure.Mode=GPIO_MODE_AF_PP;  	//复用推挽输出,用于输出PWM
		GPIO_Initure.Pull=GPIO_PULLDOWN;          //下拉
		GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
		HAL_GPIO_Init(GPIOA,&GPIO_Initure); 	
	}
}

然后是中断服务函数,等等,我们设置了自动重装填,不需要中断控制,OK结束。

实践4:定时器的输入捕获模式(结合超声波模块)

定时器初始化

us015timer.h

#ifndef _US015TIMER_H
#define _US015TIMER_H
#include "sys.h"

void TIM3_CH3_Cap_Init(u32 arr,u16 psc);

#endif

us015timer.c

#include "us015timer.h"

//  PC8---Echo     TIM3 CH3 


//TIM3  捕获初始化
TIM_HandleTypeDef TIM3_Handler;         //定时器3句柄

//定时器2通道1输入捕获配置
//arr:自动重装值(TIM3是16位的!!)
//psc:时钟预分频数
void TIM3_CH3_Cap_Init(u32 arr,u16 psc)
{  
    TIM_IC_InitTypeDef TIM3_CH3Config;  
    
    TIM3_Handler.Instance=TIM3;                          //通用定时器3
    TIM3_Handler.Init.Prescaler=psc;                     //分频系数
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器
    TIM3_Handler.Init.Period=arr;                        //自动装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
   	TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//使能自动重载
    HAL_TIM_IC_Init(&TIM3_Handler);						//初始化输入捕获时基参数
    
    TIM3_CH3Config.ICPolarity=TIM_ICPOLARITY_RISING;    //上升沿捕获
    TIM3_CH3Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//映射到TI1上
    TIM3_CH3Config.ICPrescaler=TIM_ICPSC_DIV1;          //配置输入分频,不分频
    TIM3_CH3Config.ICFilter=0;                          //配置输入滤波器,不滤波
    HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH3Config,TIM_CHANNEL_3);//配置TIM3通道3
	
    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_3);   //开启TIM3的捕获通道3,并且开启捕获中断
    __HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);   //使能更新中断
	
	HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //设置中断优先级,抢占优先级2,子优先级0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断通道  
}

//定时器2底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_IC_Init()调用
//htim:定时器2句柄
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM3_CLK_ENABLE();            //使能TIM3时钟
    __HAL_AFIO_REMAP_TIM3_ENABLE();	
	  __HAL_RCC_GPIOC_CLK_ENABLE();			//开启GPIOC时钟
	
    GPIO_Initure.Pin=GPIO_PIN_8;            //PC8
    GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; 	//复用推挽输入
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;     //高速
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);

    HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //设置中断优先级,抢占优先级2,子优先级0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //开启ITM3中断通道  
}

//捕获状态
//[7]:0,没有成功的捕获;1,成功捕获到一次.
//[6]:0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
u8  TIM3CH3_CAPTURE_STA=0;							//输入捕获状态		    				
u16	TIM3CH3_CAPTURE_VAL;							  //输入捕获值(TIM3是16位)

//定时器2中断服务函数
void TIM3_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&TIM3_Handler);				//定时器共用处理函数
}
 
//定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用,实现多个计数器满装载的计数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//更新中断(溢出)发生时执行
{
	if((TIM3CH3_CAPTURE_STA&0X80)==0)				//还未成功捕获上升沿
	{
		if(TIM3CH3_CAPTURE_STA&0X40)				//已经捕获到高电平了
		{
			if((TIM3CH3_CAPTURE_STA&0X3F)==0X3F)	//高电平太长了
			{
				TIM3CH3_CAPTURE_STA|=0X80;			//标记成功捕获了一次
				TIM3CH3_CAPTURE_VAL=0XFFFF;
			}else TIM3CH3_CAPTURE_STA++;
		}	 
	}		
}

//定时器输入捕获中断处理回调函数,该函数在HAL_TIM_IRQHandler中会被调用
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//捕获中断发生时执行
{
	if((TIM3CH3_CAPTURE_STA&0X80)==0)				//还未成功捕获
	{
		if(TIM3CH3_CAPTURE_STA&0X40)				//捕获到一个下降沿 		
		{	  			
			TIM3CH3_CAPTURE_STA|=0X80;				//标记成功捕获到一次高电平脉宽
            TIM3CH3_CAPTURE_VAL=HAL_TIM_ReadCapturedValue(&TIM3_Handler,TIM_CHANNEL_3);//获取当前的捕获值.
			TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3);   //一定要先清除原来的设置!!
            TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,TIM_ICPOLARITY_RISING);//配置TIM3通道3上升沿捕获
		}else  										//还未开始,第一次捕获上升沿
		{
			TIM3CH3_CAPTURE_STA=0;					//清空
			TIM3CH3_CAPTURE_VAL=0;
			TIM3CH3_CAPTURE_STA|=0X40;				//标记捕获到了上升沿
			__HAL_TIM_DISABLE(&TIM3_Handler);      	//关闭定时器3
			__HAL_TIM_SET_COUNTER(&TIM3_Handler,0);
			TIM_RESET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3);   //一定要先清除原来的设置!!
			TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,TIM_ICPOLARITY_FALLING);//定时器3通道3设置为下降沿捕获
			__HAL_TIM_ENABLE(&TIM3_Handler);		//使能定时器3
		}		    
	}		
}

这里由于篇幅因素,只展示定时器的设置,端口设置另一个下拉高速推挽输出,实现对超声波模块的触发即可。

这个要比输出PWM要复杂一点,主要多了中断服务函数,以及中断回调函数上,因为超声波模块要求在接收到一个上升沿的回声数据之后立刻进行数据的处理,便于在主循环或者其他中断中实现相应的避障操作。

实践5:定时器的编码器模式

对于编码电机,以前用51这种功能简单的单片机,只能使用定时器,配合特定的编码器解算算法,才能得到相对精确的电机码盘信息,但是STM32的通用定时器和高级定时器,自带编码器模式,简直是太方便了,如图:

STM32 定时器的几种基本使用_第11张图片

通过描述,可以看到,我们可以通过配置通用定时器的一个通道,实现单编码器的读取,也可以通过设置一个定时器的12两个通道,相当于硬件四倍频技术,实现高精度的编码器读取。

STM32 定时器的几种基本使用_第12张图片

编码器模式的配置

这一次我使用cubeMX进行配置,使配置过程简单明了:

STM32 定时器的几种基本使用_第13张图片

  1. GPIO引脚复用为高级定时器TIM1的两个通道CH1,CH2
  2. 选择定时器1模式为:编码器模式
  3. 分频:不分频
  4. 计数值:0xFFFF,取最大,减小循环误差(我认为是这样的)
  5. 很重要,使用12双通道,进行AB相正交编码器的硬件四倍频。

生成代码(记得勾选生成独立的h文件和C文件):

tim.h

#ifndef __tim_H
#define __tim_H
#ifdef __cplusplus
 extern "C" {
#endif

#include "main.h"

extern TIM_HandleTypeDef htim1;

void MX_TIM1_Init(void);

#ifdef __cplusplus
}
#endif
#endif

tim.c

我把原来cubeMX的注释删掉了,省略了MSP的失能函数,在重要的语句后面写了自己的注释:

/* Includes ------------------------------------------------------------------*/
#include "tim.h"

TIM_HandleTypeDef htim1;

void MX_TIM1_Init(void)
{
  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;										//计数频率:不分频
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;					//向上计数
  htim1.Init.Period = 65535;									//计数值0xFFFF
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;			//时钟预分频器:不分频
  htim1.Init.RepetitionCounter = 0;								//计数初值?
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;//重装失能
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;					//通道12,硬件四倍频
 //通道1
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;					//捕获极性:高
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;						//通道时钟分频:不分频
  sConfig.IC1Filter = 0;										//硬件滤波:不滤波
 //通道2
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;					
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0;
  
  if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}
//MSP初始化函数:被HAL_TIM_Encoder_Init调用,用于配置对应的引脚映射
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(tim_encoderHandle->Instance==TIM1)
  {
    __HAL_RCC_TIM1_CLK_ENABLE();					//定时器1时钟使能
    __HAL_RCC_GPIOA_CLK_ENABLE();					//GPIOA时钟使能

    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;	//8,9引脚
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;			//输入模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;				//无上下拉
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);			//GPIO初始化
  }
}

你可能感兴趣的:(STM32学习)