title: STM32——外设Timer定时器
date: 2020-05-20 14:43:52
tags:
categories: STM32学习记录
算法
对定时器的基本认识
先来看看这种MCU有多少定时器:函数
定时器分为3类:工具
- 基本定时器的功能最少,只能充当基本的时基,甚至都没有外部引脚。
- 通用定时器拥有基本定时器的所有功能,同时有输入捕获模式,用以接收外部的PWM,脉冲之类的信息,也有
- 高级定时器又有通用定时器的所有功能,又有互补输出模式,功能最为强大
先具体看一下夹在中间的通用定时器的官方文档中的描述:学习
为何须要定时器,而且须要这么多定时器呢?这是由于STM32的处理器是一种单线程的模式,这这时若是没有一个专门的外设,那么在软件定时期间就没法处理其余的工做。因而ST就提供了这些能够独立工做的定时器来完成须要的定时工做。ui
定时器的心脏:时基编码
一、内部时钟(CK_INT)spa
二、外部时钟模式 1:外部输入脚(TIx)线程
三、外部时钟模式 2:外部触发输入(ETR)3d
四、内部触发输入(ITRx):使用 A 定时器做为 B 定时器的预分频器(A 为 B 提供时钟)。 这些时钟,具体选择哪一个能够经过 TIMx_SMCR 寄存器的相关位来设置。code
在这里呢,最基本的定时功能就是功能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库的处理函数,咱们按照惯例,进去看看:
这个函数竟然有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个寄存器:自动重载寄存器 预分频寄存器,看看官方的公式:
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波(节选自正点原子):
假定定时器工做在向上计数 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
4个工做时用的状态寄存器TIMx_CCR1/2/3/4,其实这里只用到一个,由于通用定时器有4个通道,一个通道对应一个这种寄存器,列出来一个看看
1个使能寄存器TIMx_CCER
能够看到每两位控制一个通道,而后隔一个没用的保留位。
而后前面咱们了解到除了基本定时器,其余的定时器都是有对应引脚的,这就是重映射复用功能:
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 <sys.h> //包含引脚转换的宏函数,便于编写代码 #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的一、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通道一、4 HAL_TIM_PWM_ConfigChannel(&TIM1_Handler,&TIM1_CH14Handler,TIM_CHANNEL_4);//初始化TIM1,PWM通道一、4 HAL_TIM_PWM_Start(&TIM1_Handler,TIM_CHANNEL_1); //开启PWM通道一、4 HAL_TIM_PWM_Start(&TIM1_Handler,TIM_CHANNEL_4); //开启PWM通道一、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的通用定时器和高级定时器,自带编码器模式,简直是太方便了,如图:
经过描述,能够看到,咱们能够经过配置通用定时器的一个通道,实现单编码器的读取,也能够经过设置一个定时器的12两个通道,至关于硬件四倍频技术,实现高精度的编码器读取。
编码器模式的配置
这一次我使用cubeMX进行配置,使配置过程简单明了:
- GPIO引脚复用为高级定时器TIM1的两个通道CH1,CH2
- 选择定时器1模式为:编码器模式
- 分频:不分频
- 计数值:0xFFFF,取最大,减少循环偏差(我认为是这样的)
- 很重要,使用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初始化 } }