STM32定时器输入捕获和输出比较 + TB6612驱动电机

项目文档下载:
https://gitee.com/strayfatcat/STM32_tb6612_motor_doc
文章思路及部分素材来源:
【铁头山羊stm32 入门教程【新版】】 https://www.bilibili.com/video/BV11X4y1j7si/?p=44&share_source=copy_web&vd_source=b1aaf6b02020581bbc6e9ea7456a4af7
【[铁头山羊stm32入门教程] 7.2. 定时器之输入捕获(上)】 https://www.bilibili.com/video/BV14p421Q7DV/?share_source=copy_web&vd_source=b1aaf6b02020581bbc6e9ea7456a4af7
【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p=19&share_source=copy_web&vd_source=b1aaf6b02020581bbc6e9ea7456a4af7

前置知识:定时器定时

STM32通用定时器框图如下:
在这里插入图片描述
图中英文简称解释:

  • CK_PSC:定时器时钟源
  • PSC:预分频值,16位寄存器最大65535
  • CK_CNT:计数器CNT的计数频率
  • CNT:定时器计数器
  • ARR:自动重装载寄存器

从图中可看出,定时器得到时钟源CK_PSC后,先要经过预分频器PSC的分频,然后得到CNT计数器每秒计数的频率CK_CNT(单位Hz)。此时定时器开始向上或向下计数,当计数器CNT的值与自动重装载寄存器ARR的值相等时会触发中断,可以简单理解为触发定时中断。此时从计数器CNT开始计数到定时器触发中断的时间,就是定时器定时时间。

总计以上可得出定时器定时时间流程:

  1. 时钟源CK_PSC首先经过预分频PSC
  2. 预分频后得到计数器CNT计数频率
  3. 计数频率乘以期望计数时间(单位:秒)可以得出ARR的值

定时器计数频率:
单位:赫兹Hz(次/s)
通过预分频器的值控制,决定定时器计数器CNT计数的速度

Freq = Tclk / (PSC + 1)

定时器溢出时间:
单位:秒 s
由定时器计数器CNT频率控制

Tout = (ARR + 1) / Freq

根据上述两个式子可以推出
定时器定时公式:

Tout = (ARR + 1) * (PSC + 1) / Tclk

Tout:定时时间(单位s)
Tclk:系统主控频率(单位Hz)

  • 例如:定时10ms,主控频率72Mhz,求ARR和PSC的值。

72MHz 换算单位得 72 000 000 Hz
带入得

0.01s = (ARR + 1) * (PSC + 1) / 72 000 000Hz

一般选择 (PSC+1) 使分频后计数频率为一个方便的整数(如 1 MHz),便于计算 ARR。这里取7200
则计算得 (ARR + 1) = 100
故可得PSC=7199,ARR=99

定时器10ms定时示例代码:

/**
 * 函    数:系统定时器初始化
 * 参    数:无
 * 返 回 值:无
 */
void timer_init(void)
{
	// 打开apb1时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	// 选择内部时钟作为中断
	TIM_InternalClockConfig(TIM2);
	// 初始化定时器配置
	TIM_TimeBaseInitTypeDef TIM_TimeInitStruceture;
	TIM_TimeInitStruceture.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeInitStruceture.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeInitStruceture.TIM_Period = 7200 - 1;
	TIM_TimeInitStruceture.TIM_Prescaler = 100 - 1;
	TIM_TimeInitStruceture.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeInitStruceture);

	/** 清除标志位,解决系统重启后数字从1开始自加 **/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	// 启动更新中断
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // NVIC分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	// 配置NVIC
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStructure);

	// 启动定时器
	TIM_Cmd(TIM2, ENABLE);
}

/* 定时器中断函数 复制到主函数使用
void TIM2_IRQHandler(void){

	//判断标志位
	if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){


		//清除标志位
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}
*/

一、定时器输出比较(输出PWM波)

1.是什么PWM?

全称Pulse Width Modulation:脉冲宽度调制(简称脉宽调制,通俗的讲就是调节脉冲的宽度),是电子电力应用中非常重要的一种控制技术。可以通过对一系列脉冲的宽度进行调制,来有效地获得所需要的模拟参量,常应用于电机控速等领域。可以等效实现模拟信号的输出。

应用场景:必须是一个惯性系统

重要参数

  • 频率 = 1 / TS。TS指一个高低电平变换周期的时间。频率越快,频率值越小,等效模拟的信号越平稳,性能开销越大。

在这里插入图片描述

  • 占空比 = TON / TS。TON指高电平时间,TS是周期时间。高电平占整个周期时间的比例。占空比决定了PWM等效出来的模拟电压大小,占空比越大,等效的模拟电压就越趋近于高电平

在这里插入图片描述

  • 分辨率 = 占空比变化步距。有的步距只能是1%,2%,3%这样以1%的步距跳变,则分辨率就是1%。也就是占空比的精细程度。例如LED的分辨率为1%即说明LED的亮度是1%亮,2%亮这样一直到100%亮

2.PWM参数计算

PWM频率:

Freq = CK_PSC / (PSC + 1) / (ARR + 1)

PWM占空比:

Duty = CCR / (ARR + 1) * 100%

PWM分辨率:

Reso = 1 / (ARR + 1)

例如:系统时钟 72 MHz,设定 PWM 频率 10 kHz,分辨率 ≥ 0.05%。
问:

  • PSC、ARR 各取多少合适?

10 000 = 72 000 000 / (PSC+1)/(ARR+1) 化简可得 (PSC+1)(ARR+1) = 7200

因为分辨率≥0.05%,即1 / (ARR+1) ≥ 0.0005 可得 ARR+1≥2000

一般选择 (PSC+1) 使分频后计数频率为一个方便的整数(如 1 MHz),便于计算 ARR。这里取PSC=2(不固定,但要求整数)

带入得出ARR=2399

所以ARR = 2399,PSC = 2

  • 对应的 CCR 若要输出 60% 占空比,应为多少?

0.6 = CCR / (2399+1) 得CCR = 1440

3.定时器输出比较配置解析

以下是定时器配置生成PWM的基本流程,这里我们使用PWM生成模式1

在这里插入图片描述

图解CNT(ARR)、CCR和PWM输出的关系:

在这里插入图片描述

上图中,第一个坐标系为CNT与时间的关系,其中横坐标为CNT的值(设置为99,CCR的值为30);第二个坐标系为输出电平与时间的关系。因为我们使用PWM生成模式1,所以当CNT的值小于CCR的值时,GPIO口输出高电平;同理当CNT的值大于CCR时,GPIO口输出低电平。当CNT的值累加到99时,会触发定时器的自动重装载,会自动将ARR中的值赋给CNT,并重新开始新一轮的生成。所以对应输出电平与时间坐标系,可以明显看出PWM的一个周期,图中时30%的高电平时间,70%低电平时间。

所以我们可以得出以下结论:

PWM的本质就是通过定时器控制输出高低电平的时间。高电平时间占一个周期里的比重越大,LED灯(或者电机)就越亮(转速越快)

以下是定时器生成pwm代码:

pwm.c

#include "stm32f10x.h"
#include "pwm.h"
/**
 * 函    数:PWM初始化函数
 * 参    数:无
 * 返 回 值:无
 * 说    明:初始化pwm,PSC=35,ARR=999
 *          pwm频率:20KHz,pwm占空比:0-100%,pwm分辨率:1%
 */
void pwm_init(void)
{
    /* 开启定时器1和GPIO口时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);  // 开启TIM1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA时钟

    /* 初始化PWM引脚 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;       // PA8引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 初始化定时器结构体 */
    TIM_TimeBaseInitTypeDef TIM_InitStructure;              // 定义初始化结构体
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频 不分频
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式 向上计数
    TIM_InitStructure.TIM_Period = 100 - 1;                 // Arr 自动重装载值 与占空比有关
    TIM_InitStructure.TIM_Prescaler = 36 - 1;               // PSC 预分频周期
    TIM_InitStructure.TIM_RepetitionCounter = 0;            // 重复计数器 高级定时器功能
    TIM_TimeBaseInit(TIM1, &TIM_InitStructure);

    /* 初始化输出比较 */
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);                       // 初始化结构体变量
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             // 输出比较模式,选择PWM模式1
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     // 输出极性,选择为高,若选择极性为低,则输出高低电平取反
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
    TIM_OCInitStructure.TIM_Pulse = 0;                            // 初始的CCR值
    TIM_OC1Init(TIM1, &TIM_OCInitStructure);                      // 配置TIM1的输出比较通道1

    /* 高级定时器使能主输出 */
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
    /* TIM使能 */
    TIM_Cmd(TIM1, ENABLE);
}

/**
 * 函    数:PWM设置CCR
 * 参    数:Compare 要写入的CCR的值,范围:0~100
 * 返 回 值:无
 * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
 *           占空比Duty = CCR / (ARR + 1)
 */
void pwm_set_compare1(int8_t Compare)
{
    TIM_SetCompare1(TIM1, Compare); // 设置CCR
}

pwm.h

#ifndef __PWM_H__
#define __PWM_H__

void pwm_init(void);
void pwm_set_compare1(int8_t Compare);

#endif

二、定时器输入捕获(读取编码器)

输入捕获详解

定时器输入捕获部分结构详解,图片来自bilibili铁头山羊

在这里插入图片描述

图中可以看出,配置输入捕获功能所需的五个寄存器,分别是:通道选择、输入滤波、边沿检测、信号选择和分频系数

分别对应库函数的TIM_ICInitTypeDef结构体里的参数

typedef struct
{
  uint16_t TIM_Channel;      /* 通道选择 */
  uint16_t TIM_ICPolarity;   /* 边沿检测 */
  uint16_t TIM_ICSelection;  /* 信号选择 */
  uint16_t TIM_ICPrescaler;  /* 分频系数 */
  uint16_t TIM_ICFilter;     /* 输入滤波 值范围:0x0 - 0xF */
} TIM_ICInitTypeDef;

对应寄存器功能的解释:

通道选择:选择外部信号输入的通道,可选通道有CH1-CH4,其中CH1和CH2为一组CH3和CH4为一组通道

输入滤波:对输入信号进行滤波,滤除电平信号中的毛刺信号,保证输入信号的纯净。

边沿检测:该寄存器会检测输入信号的上下边沿信号并输出对应的信号。

信号选择:选择由哪个边沿检测寄存器作为输入信号,直接:从对应当前通道直接输入;间接:从通道组的另一个通道输入

分频系数:选择边沿触发的频率,例如:选择1分频,当出现一个上升/下降沿就会触发事件,选择2分频,当累计出现两个上升/下降沿就会触发事件

  • pwm频率计算:

测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率fx=fc / N

  • pwm占空比计算:

CCR2寄存器的值 * 100 / CCR1寄存器的值
CCR2寄存器中记录的是每一个下降沿到来时CNT中的计数值
CCR1寄存器中记录的是每一个上升沿到来时CNT中的计数值

以下代码是输入捕获读取第一节的pwm频率代码

ic_pwm.c

#include "stm32f10x.h"
#include "ic_pwm.h"

/**
 * 函    数:输入捕获初始化函数
 * 参    数:无
 * 返 回 值:无
 * 说    明:无
 */
void ic_pwm_init(void)
{
    /* 开启 TIM3 和 GPIOA 时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);  // 开启TIM3时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA时钟

    /* 初始化PWM引脚 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;     // PA6 引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 初始化定时器时基单元结构体 */
    TIM_TimeBaseInitTypeDef TIM_InitStructure;              // 定义初始化结构体
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频 不分频
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式 向上计数
    TIM_InitStructure.TIM_Period = 65536 - 1;               // Arr 自动重装载值
    TIM_InitStructure.TIM_Prescaler = 72 - 1;               // PSC 预分频周期
    TIM_InitStructure.TIM_RepetitionCounter = 0;            // 重复计数器 高级定时器功能
    TIM_TimeBaseInit(TIM3, &TIM_InitStructure);

    /* 初始化输入捕获 */
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;                // 输入通道 通道一
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                         // 滤波器
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 信号交叉选择 直接输入
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;     // 极性选择 上升沿触发
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;           // 捕获分配器 不分频

    /* 使用PWM模式初始化,这个函数会自动配置好CH1和CH2 */
    TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); // 此函数同时会把另一个通道配置为相反的配置,实现PWMI模式 当然也可以手动配置

    /*选择触发源及从模式*/
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);    // 触发源选择TI1FP1
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // 从模式选择复位 即TI1产生上升沿时,会触发CNT归零

    /* TIM使能 */
    TIM_Cmd(TIM3, ENABLE);
}

/**
 * 函    数:获取输入捕获的频率
 * 参    数:无
 * 返 回 值:捕获得到的频率
 */
uint32_t ic_pwm_get_freq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1); // 测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

/**
 * 函    数:获取输入捕获的占空比
 * 参    数:无
 * 返 回 值:捕获得到的占空比
 */
uint32_t ic_pwm_get_duty(void)
{
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); // 占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}

ic_pwm.h

#ifndef __IC_PWM_H__
#define __IC_PWM_H__

void ic_pwm_init(void);
uint32_t ic_pwm_get_freq(void);
uint32_t ic_pwm_get_duty(void);

#endif

测试结果:
在这里插入图片描述

三、TB6612驱动电机

TB6612介绍

在这里插入图片描述

TB6612FNG 是东芝半导体公司生产的一款直流电机驱动器件,它具有大电流 MOSFET-H 桥结构,双通道电路输出,可同时驱动 2 个电机。它具有很高的集成度,同时能提供足够的输出能力,运行性能和能耗方面也具有优势,因此在集成化、小型化的电机控制系统中,它可以作为理想的电机驱动器件。

驱动原理如下图
在这里插入图片描述

由上图总结可以得出:

一块TB6612可以同时驱动两路电机并读取他们的编码器。

STBY通过输出高低电平用于控制电机启动和停止;AIN1、AIN2口通过输入高低电平来控制电机转动的方向;PWMA口通过输入占空比不同的PWM信号来控制电机转动速度;AO1、AO2用于读取电机编码器,通过编码器数值测速(电机有编码器才能用)

本人采用趣轮科技的D153B电机驱动模块和MG513减速直流电机(霍尔编码器)实现,以下是电机相关代码

PWM频率:20KHz,PWM占空比0~100%,PWM分辨率:1%

pwm采用第一节的代码

motor.c

#include "stm32f10x.h"
#include "motor.h"

/**
 * 函    数:电机初始化函数
 * 参    数:无
 * 返 回 值:无
 * 说    明:初始化电机引脚
 */
void motor_init(void)
{
    /* 开启GPIOA时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* 初始化电机引脚 用于控制电机转动方向 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        // 推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // PA9和PA10
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 初始化PWM 用于控制电机旋转速度 */
    pwm_init();
}

/**
 * 函    数:直流电机设置速度
 * 参    数:Speed 要设置的速度,范围:-100~100
 * 返 回 值:无
 * 说    明:规定电机正转为逆时针方向,反转为顺时针方向
 */
void motor_set_speed(int8_t Speed)
{
    if (Speed >= 0) // 正转速度
    {
        GPIO_SetBits(GPIOA, GPIO_Pin_9);    // PA9高电平
        GPIO_ResetBits(GPIOA, GPIO_Pin_10); // PA10低电平
        pwm_set_compare1(Speed);            // PWM设置
    }
    else if (Speed < 0) // 反转速度
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_9); // PA9低电平
        GPIO_SetBits(GPIOA, GPIO_Pin_10);  // PA10高电平
        pwm_set_compare1(-Speed);          // PWM设置 PWM只能给正数 负方向由GPIO的高低电平设置
    }
}

motor.h

#ifndef __MOTOR_H__
#define __MOTOR_H__

#include "pwm.h"

void motor_init(void);
void motor_set_speed(int8_t Speed);


#endif

三、读取电机编码器测速

使用定时器输入捕获功能的编码器模式,对应图中圈出的部分。当使用定时器的编码器模式时,有以下注意事项:

  • 使用编码器功能,定时器会接管部分寄存器,所以有些寄存器不需要手动配置
  • 每个定时器的编码器功能通道只能使用CH1和CH2,CH3和CH4不能作为编码器通道

在这里插入图片描述

配置代码如下:

encoder.c

#include "stm32f10x.h"

/**
 * 函    数:编码器功能初始化
 * 参    数:无
 * 返 回 值:无
 * 说    明:无
 */
void encoder_init(void)
{
    /* 开启GPIOA 和 TIM3 时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    /* GPIO初始化 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          // 上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // PA6 PA7
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* TIM3 时基单元初始化 */
    TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
    TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频 不分频
    TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式 当选择定时器编码器模式时,该配置无效
    TIM_BaseInitStructure.TIM_Period = 65536 - 1;               // ARR自动重装载值 设置最大值
    TIM_BaseInitStructure.TIM_Prescaler = 1 - 1;                // PSC分频系数 不分频
    TIM_BaseInitStructure.TIM_RepetitionCounter = 0;            // 重复计数器 高级定时器功能
    TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);

    /* TIM3 输入捕获模块初始化 */
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICStructInit(&TIM_ICInitStructure); // 结构体初始化参数
    /* 通道一 */
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 输入捕获通道选择 通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;          // 滤波器配置 最大
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    /* 通道二 */
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 输入捕获通道选择 通道2
    TIM_ICInitStructure.TIM_ICFilter = 0xF;          // 滤波器配置 最大
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    /*     以下参数不会被使用
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 输入模式(直接/间接)
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 极性选择
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 分频系数
    */

    /* 编码器模式配置 */
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); // 计数模式 TI12同时计数 此时极性选择代表两个通道极性不反转

    /* 启动TIM3 */
    TIM_Cmd(TIM3, ENABLE);
}

/**
 * 函    数:读取编码器CNT值并清零
 * 参    数:无
 * 返 回 值:编码器的CNT值
 * 说    明:无
 */
int16_t encoder_get(void)
{
    int16_t temp;                // 临时变量
    temp = TIM_GetCounter(TIM3); // 获取CNT的值
    TIM_SetCounter(TIM3, 0);     // 清零CNT
    return temp;
}

encoder.h

#ifndef __ENCODER_H__
#define __ENCODER_H__

void encoder_init(void);
int16_t encoder_get(void);


#endif

效果如下:
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐