STM32学习笔记04-TIM定时器

目录

TIM简介

定时器类型

基本定时器

通用定时器

高级定时器

TIM定时中断

定时中断基本结构

时序图

预分频器时序

计数器时序

计数器无预装时序

计数器有预装时序

RCC时钟树

定时器定时中断应用

定时器外部时钟应用

TIM输出比较

输出比较简介

PWM简介

输出比较通道(通用)

PWM基本结构

输出比较通道(高级)(了解)

PWM应用

PWM驱动LED呼吸灯

PWM驱动舵机

 舵机简介

代码分析

PWM驱动直流电机

 直流电机及驱动简介

代码分析

TIM输入捕获

频率测量

输入捕获通道

主从触发模式

输入捕获基本结构

PWMI基本结构

输入捕获模式应用-测频率

PWMI模式测频率占空比

TIM编码器接口

正交编码器

编码器接口基本结构

工作模式

实例

(TI1TI2均不反相)

(TI1反相)

编码器接口应用-编码器接口测速


TIM简介

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时(STM32中,定时器的基准时钟一般是主频72MHz,如果对72MHz计72个数,那就是1MHz也就是1微秒时间,如果计72000个数,那就是1KHz也就是1ms时间,如果预分频器设置最大,自动重装也设置最大,72M/65536/65536得的是中断频率,再取倒数,就是59.65秒多)
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

  • STM32F103C8T6定时器资源:TIM1TIM2TIM3TIM4

基本定时器

 下面简单介绍一下主模式触发DAC,后面会细讲:

我们在使用DAC时,可能会用DAC输出一段波形,那就需要每隔一段时间来触发一次DAC,让它输出下一个电压点,如果用正常的思路来实现就是先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,这样会使主程序处于频繁中断的状态,这会影响主程序的运行和其他中断的响应。所以定时器就设计了一个主模式,使用这个主模式可以把定时器的更新事件映射到触发输出TRGO(Trigger Out)的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要通过中断来触发DAC转换了,仅需要把更新事件通过主模式映射到TRGO,然后TRGO就会直接去触发DAC了。整个过程不需要软件参与,实现了硬件自动化,这就是主模式的作用。

通用定时器

高级定时器

TIM定时中断

定时中断基本结构

时序图

现在看几个时序图,研究一下时基单元运行的一些细节问题。

预分频器时序

  • 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计数器时序

  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)

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

用72MHz/(PSC+1)/(ARR+1)就能得到溢出频率,如果计算溢出时间,取个倒数就好了。计数器也是有缓冲机制的。结构体里有阴影的寄存器都是有影子寄存器这样的缓存机制的。并且这个缓存寄存器用不用是可以自己设置的。

计数器无预装时序

计数器有预装时序

RCC时钟树

STM32的RCC时钟树较为复杂,它主要由振荡源、锁相环等产生一定频率的信号,作为系统时钟、看门狗时钟等,同时将系统时钟分配给各个外设,驱动各外设工作。是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统。

首先看时钟产生电路:

SystemInit函数里,ST配置时钟的过程:

再看看时钟分配电路:

定时器定时中断应用

在System文件夹新建Timer.c和Timer.h文件。在Timer.c写Timer_Init()函数初始化定时器。

初始化步骤根据定时中断基本结构来完成:

整个模块配置完成后,我们还需要使能一下计数器,要不然计数器是不会运行的,当定时器使能后计数器就会开始计数了,当计数器更新时,触发中断。最后我们在写一个定时器的中断函数,这样这个中断函数每隔一段时间就能自动执行一次了。

先介绍几个TIM的库函数:

/*
* 将TIMx外设寄存器初始化为其默认重置值。
* 参数 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 返回值:无
*/
void TIM_DeInit(TIM_TypeDef* TIMx);

时基单元: 

/*
* 根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx时基单元外设。
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 参数2 TIM_TimeBaseInitStruct:指向TIM_TimeBaseInitTypeDef的指针,包含指定的TIM外设的配置信息。
* 返回值:无
*/
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
/*
* 用默认值填充每个TIM_TimeBaseInitStruct成员。
* 参数 TIM_TimeBaseInitStruct:指向将被初始化的TIM_TimeBaseInitTypeDef的指针。
* 返回值:无
*/
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

运行控制: 

/*
* 启用或禁用指定的TIM外设。
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIMx外设。
* 参数2 NewState: TIMx外设的新状态。取值为:ENABLE或DISABLE。
* 返回值:无
*/
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

中断输出控制: 

/*
* 启用或禁用指定的TIM中断。
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIMx外设。
* 参数2 TIM_IT:启用或禁用的TIM中断源。
* 参数3 NewState: TIM中断的新状态。取值为:ENABLE或DISABLE。
* 返回值:无
*/
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

时基单元的时钟选择部分(可选RCC内部时钟、ETR外部时钟、ITRx其他定时器、TIx捕获通道):

/*
* 配置TIMx内部时钟
* 参数 TIMx:选择TIM外设,其中x可以是1、2、3、4、5、8、9、12或15。
* 返回值:无
*/
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

 

/*
* 配置TIMx内部触发器为外部时钟(选择ITRx其他定时器的时钟)
* 参数1 TIMx:用来选择TIM外设,其中x可以是1、2、3、4、5、9、12或15。
* 参数2 TIM_ITRSource:触发源(选择要接入哪个其他定时器)。
* 返回值:无
*/
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

/*
* 选择TIx捕获通道的时钟
* 参数1 TIMx:用来选择TIM外设,其中x可以是1、2、3、4、5、9、12或15。
* 参数2 TIM_TIxExternalCLKSource:触发源。(选择TIx具体的某个引脚)
* 参数3 TIM_ICPolarity: TIx极性。
* 参数4 ICFilter:滤波器。取值范围为0x0 ~ 0xF。
* 返回值:无
*/
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);

 

/*
* 配置外部时钟模式1(选择ETR通过外部时钟模式1输入的时钟)
* 参数1 TIMx:选择TIM外设,其中x可以为1、2、3、4、5或8。
* 参数2 tim_exttrgprecaler:外部触发器预分频器。(对ETR外部时钟再提前做一个分频)
* 参数3 TIM_ExtTRGPolarity:外部触发极性。
* 参数4 ExtTRGFilter:外部触发滤波器。取值范围为0x00 ~ 0x0F
* 返回值:无
*/
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t         TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);

/*
* 配置外部时钟模式2(选择ETR通过外部时钟模式2输入的时钟)
* 参数1 TIMx:选择TIM外设,其中x可以为1、2、3、4、5或8。
* 参数2 tim_exttrgprecaler:外部触发器预分频器。(对ETR外部时钟再提前做一个分频)
* 参数3 TIM_ExtTRGPolarity:外部触发极性。
* 参数4 ExtTRGFilter:外部触发滤波器。取值范围为0x00 ~ 0x0F
* 返回值:无
*/
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter)

/*
* 单独配置TIMx外部触发器(ETR)的预分频器、极性、滤波器
* TIMx:选择TIM外设,其中x可以为1、2、3、4、5或8。
* tim_exttrgprecaler:外部触发器预分频器。
* TIM_ExtTRGPolarity:外部触发极性。
* ExtTRGFilter:外部触发滤波器。取值范围为0x00 ~ 0x0F
* 返回值:无
*/
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);

更改关键参数的函数:

/*
* 配置TIMx预分频器。(单独写预分频器值)
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 参数2 precaler:预分频器寄存器的值
* 参数3 TIM_PSCReloadMode: 写入的模式(结合缓冲机制)。该参数可以是以下值之一:
*    TIM_PSCReloadMode_Update:在更新事件中加载预分频值。
*    TIM_PSCReloadMode_Immediate:立即加载预分频值。
* 返回值:无
*/
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
/*
* 指定要使用的TIMx计数器模式。
* 参数1 TIMx:选择TIM外设,其中x可以为1、2、3、4、5或8。
* 参数2 TIM_CounterMode:要使用的计数器模式。(向上、向下、中心对齐)
* 返回值:无
*/
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
/*
* 自动重装器预装功能配置。
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 参数2 NewState: TIMx外设预装寄存器的新状态。取值为:ENABLE或DISABLE
* 返回值:无
*/
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

/*
* 设置TIMx计数器寄存器值
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 参数2 Counter:计数器寄存器的新值。
* 返回值:无
*/
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
/*
* 设置TIMx自动重装寄存器值
* 参数1 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 参数2 Autoreload:自动重装寄存器的新值。
* 返回值:无
*/
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

/*
* 获取TIMx计数器值。
* 参数 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 返回值:计数器的值。
*/
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
/*
* 获取TIMx预分频器值。
* 参数 TIMx:其中x为1 ~ 17,表示选择TIM外设。
* 返回值:预分频器的值
*/
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

接下来我们按步骤配置一下定时器吧!以TIM2通用定时器为例:

第一步,RCC开启时钟。由于TIM2挂在APB1外设总线上,所以我们使用RCCAPB1外设时钟控制函数。

/* 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

第二步,选择时基单元的时钟源。我们想选内部时钟,前文介绍的TIM_InternalClockConfig()。

/* 选择时基单元时钟 */
TIM_InternalClockConfig(TIM2);

这样,TIM2的时基单元就由内部时钟来驱动了。(定时器默认使用的就是内部时钟,不调这个函数也是使用内部时钟,知道一下)

第三步,配置时基单元。TIM_TimeBaseInit()初始化时基单元。结构体有五个成员。

  • TIM_ClockDivision,指定时钟分频
  • TIM_CounterMode,计数器模式(向上计数、向下计数、中央对齐)
  • TIM_Period,周期,ARR自动重装器的值
  • TIM_Prescaler,PSC预分频器的值
  • TIM_RepetitionCounter,重复计数器的值,高级定时器才有

决定定时时间的参数计算,如果我们想定一个1s的时间,参考

  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)

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

定时频率=72M/(PSC+1)/(ARR+1),定时1s也就是定时频率为1Hz,那我们就可以给PSC给7200-1,ARR给10000-1,这样72000000/7200/10000就是1Hz。值不唯一,可以预分频给少点,自动重装给多点,这样就是以较高频率计比较多的数。也可以预分频给多点,自动重装给少点,这样就是比较低的频率计比较少的数。

/* 配置时基单元 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//周期,ARR自动重装器的值(计10000次)
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

第四步,配置输出中断控制,允许更新中断输出到NVIC。

/* 使能中断 */
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//避免初始化完就进入中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);

第五步,配置NVIC。上一课配置过不细说了:STM32学习笔记03-EXTI外部中断-CSDN博客

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
NVIC_Init(&NVIC_InitStructure);

第六步,运行控制。启动定时器。

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

到这里,整个定时中断的初始化代码就完成了,接下来就可以写中断函数了。启动文件找到定时器2的中断函数:

 

进入中断先检查中断标志位,最后记得清除标志位。 

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Num ++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

 完整代码:

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint8_t Num;

int main(void)
{
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	while(1)
	{
		OLED_ShowNum(1,5,Num,5);
	}
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Num ++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

Timer.c:

#include "stm32f10x.h"                  // Device header


void Timer_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM2);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//周期,ARR自动重装器的值(计10000次)
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/* 使能中断 */
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//避免初始化完就进入中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	/* 启动定时器 */
	TIM_Cmd(TIM2,ENABLE);
}

Timer.h:

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

定时器外部时钟应用

对射式红外传感器DO数字输出接到PA0引脚,这个PA0引脚就是TIM2的ETR引脚,我们就在这个引脚输入一个外部时钟。

在上一个代码的基础上更改,第一步开启时钟不变。要用到GPIO,使用先配置好GPIO,我选择上拉输入。由引脚定义表,CH1通道在PA0,初始化GPIO_Pin_0;

第二步选择时基单元时钟源我们不使用内部时钟了,使用TIM_ETRClockMode2Config()函数,这是我们想要的通过ETR引脚的外部时钟模式2配置。

第一个参数是TIM2,第二个参数是外部触发预分频器,我们不需要分频,选OFF的。第三个参数外部触发的极性,我选择不反向(高电平或上升沿有效)。第四个参数是外部触发滤波器,这个值必须是0x00-0xF之间的,暂时不用滤波器,写0x00。 

/* 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
	
/* 选择时基单元时钟 */
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);

 第三步,配置时基单元,预分频和自动重装值改小点,我们手动模拟的没这么快。自动重装值给10-1,从0计到9。

/* 配置时基单元 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 0;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

后面几步不需要更改。完整代码:

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint8_t Num;

int main(void)
{
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	OLED_ShowString(2,1,"COUNT:");
	while(1)
	{
		OLED_ShowNum(1,5,Num,5);
		OLED_ShowNum(2,5,Timer_GetCounter(),5);
	}
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Num ++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

Timer.c:

#include "stm32f10x.h"                  // Device header


void Timer_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 0;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/* 使能中断 */
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//避免初始化完就进入中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	/* 启动定时器 */
	TIM_Cmd(TIM2,ENABLE);
}

uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);
}










Timer.h:

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);
uint16_t Timer_GetCounter(void);

#endif

TIM输出比较

输出比较简介

  • OC(Output Compare)输出比较
  • 输出比较可以通过比较CNTCCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能

PWM简介

  • PWM(Pulse Width Modulation)脉冲宽度调制
  • 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
  • PWM参数:

     频率 = 1 / TS            占空比 = TON / TS           分辨率 = 占空比变化步距

输出比较通道(通用)

接下来看一下输出模式控制器里面的执行逻辑。这个模式控制器的输入是 CNT与CCR的大小关系,输出是REF的高低电平,里面可以选择多种模式来更加灵活地控制REF输出,这个模式可以通过寄存器来进行配置。具体看下表:

 

PWM基本结构

我们一般用PWM模式1向上计数,那这种模式是怎么输出频率和占空比都可调的PWM波形呢?我们在看一下PWM基本结构:

 下面看一下PWM的参数是如何计数的:

PWM频率等于计数器更新频率。

  • PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  • PWM占空比:  Duty = CCR / (ARR + 1)
  • PWM分辨率:  Reso = 1 / (ARR + 1)

例如我需要输出一个频率为1KHz,占空比50%,且分辨率为1%的PWM波形。

  • 72M/(PSC+1)/(ARR+1) = 1000
  • CCR/(ARR+1) = 50%
  • 1/(ARR+1) = 1%

解得:ARR = 100-1,CCR = 50,PSC+1 = 720-1。

输出比较通道(高级)(了解)

PWM应用

PWM驱动LED呼吸灯

我们先通过PA0引脚输出一个频率为1KHz,占空比50%,且分辨率为1%的PWM波形,关于参数计算前文已经计算过了,ARR = 100-1,CCR = 50,PSC = 720-1。完成这个之后再实现任意占空比完成呼吸灯。

在Hardware文件夹新建PWM.c和PWM.h,在PWM.c中写PWM初始化函数PWM_Init()。

下面介绍几个函数,之前介绍过的就不重复了:

/*
* 根据TIM_OCInitStruct指定的参数初始化TIMx Channel(输出比较通道)。
* 参数1 TIMx:其中x除6、7外可选1 ~ 17,用于选择TIM外设。
* 参数2 TIM_OCInitStruct:指向TIM_OCInitTypeDef结构体的指针包含指定TIM外设的配置信息。
* 返回值:无
*/
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
/*
* 用默认值填充每个TIM_OCInitStruct成员。
* 参数 TIM_OCInitStruct:指向被初始化的TIM_OCInitTypeDef结构体的指针。
* 返回值:无
*/
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

/*
* 单独更改CCR寄存器值的函数
* 参数1 TIMx:其中x除6、7外可选1 ~ 17,用于选择TIM外设。
* 参数2 Compare:表示捕获Compare寄存器的新值。
*返回值:无
*/
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

补充:

/*
* 启用或禁用TIM外设主输出。
* 仅高级定时器使用,在使用高级定时器输出PWM时需要调用使能主输出,否则PWM不能正常输出
*/
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

 初始化步骤根据PWM结构图:

 

开启时钟、时钟源选择、配置时基单元代码不在细讲:

/* 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	

/* 选择时基单元时钟 */
TIM_InternalClockConfig(TIM2);
	
/* 配置时基单元 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

第三步,配置输出比较单元。我们使用PA0口,对应第一个输出比较通道,使用TIM_OC1_Init()。

第一个参数给TIM2,第二个参数是结构体,它有很多个成员,我们先调用TIM_OCStructInit()给结构体赋初始值,在列出我们需要的成员。

  • TIM_OCMode,输出比较模式(冻结模式、相等时置有效电平....)
  • TIM_OCPolarity,输出比较极性(高极性也就是极性不翻转REF波形直接输出或者说有效电平是高电平REF有效时输出高电平,这都是一个意思。低极性,就是REF电平取反或者说有效电平为低电平)
  • TIM_OutputState,输出使能
  • TIM_Pulse,设置CCR寄存器值
/* 初始化输出比较单元 */
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = 50;//设置CCR寄存器值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);

第四步配置GPIO、第五步运行控制

/* 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
	
/* 选择时基单元时钟 */
TIM_InternalClockConfig(TIM2);
	
/* 配置时基单元 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
/* 初始化输出比较单元 */
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = 50;//设置CCR寄存器值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	
/* 启动定时器 */
TIM_Cmd(TIM2,ENABLE);

 此时,一个频率为1KHz,占空比50%,且分辨率为1%的PWM波形就通过PA0输出了。

要呈现呼吸灯,那就是不断更改CCR的值,在运行过程更改CCR,使用TIM_SetCompare1()。

我们写一个函数封装:

void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2,Compare);
}

在main.c主循环不断更改CCR的值即可:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i;

int main(void)
{
	OLED_Init();
	PWM_Init();
	while(1)
	{
		for(i = 0;i<=100;i++)
		{
			PWM_SetCompare1(i);
			Delay_ms(10);
		}
        for(i = 0;i<=100;i++)
		{
			PWM_SetCompare1(100-i);
			Delay_ms(10);
		}
		
	}
}

PWM驱动舵机

 舵机简介

  • 舵机是一种根据输入PWM信号占空比来控制输出角度的装置
  • 输入PWM信号要求:周期为20ms(50Hz),高电平宽度为0.5ms~2.5ms

代码分析

完成按键控制舵机旋转,每按一次按键舵机旋转30°,大于180回到0°。

在上一个代码的基础上更改,我们现在使用的是PA1口,引脚定义表知是通道2,使用GPIO初始化为GPIO_Pin_1

后面的TIM_OC1Init是通道1初始化,我们改成OC2_Init()。封装的改变CCR寄存器的函数里的SetCompare1改成SetCompare2,头文件也别忘了。这样就能使用通道2了。下面计算参数。

  • 72M/(PSC+1)/(ARR+1) = 50
  • CCR/(ARR+1) = 占空比
  • 1/(ARR+1) = 分辨率

ARR、PSC和CCR。舵机要求的频率是20ms,也就是50Hz。占空比这里舵机要求高电平时间是0.5ms~2.5ms,PSC与ARR的值不固定,尽量找一个方便计算的值,设置PSC为72-1,ARR为20K-1,这样72M/72/20000 = 50,同时20K对应20ms,那CCR设置500占空比就是0.5ms,CCR设置成2500占空比就是2.5ms。

PWM.c:

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM2);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/* 初始化输出比较单元 */
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;//设置CCR寄存器值
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	
	/* 启动定时器 */
	TIM_Cmd(TIM2,ENABLE);
}

void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2,Compare);
}

建立舵机模块,完成按键控制舵机旋转:

Servo.c:

#include "stm32f10x.h"                  // Device header
#include "PWM.h" 



void Servo_Init(void)
{
	PWM_Init();
}

void Servo_SetAngle(float Angle)
{
    //0-180 到 500-2500的映射
	PWM_SetCompare2(Angle / 180 * 2000 + 500);
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;
float Angle;

int main(void)
{
	OLED_Init();
	Key_Init();
	Servo_Init();
	
	OLED_ShowString(1,1,"Angle:");
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Angle += 30;
			if(Angle > 180)
			{
				Angle = 0;
			}
		}
		Servo_SetAngle(Angle);
		OLED_ShowNum(1,8,Angle,3);
	}
}

PWM驱动直流电机

 直流电机及驱动简介

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

代码分析

按键控制直流电机转速。

在驱动呼吸灯的基础上修改,电机在PA2,由引脚定义表知是通道3,GPIO_Pin_0改成Pin_2,OC1_Init改成OC3_Init,SetCompare1改成SetCompare3。

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM2);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/* 初始化时基单元 */
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse = 50;//设置CCR寄存器值
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);
	
	/* 启动定时器 */
	TIM_Cmd(TIM2,ENABLE);
}

void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2,Compare);
}

写一个电机模块Motor。

Motor_Init()调用底层的PWM_init()初始化PWM,同时在电机模块里多了电机方向控制的两个脚,所以在这里还要初始化方向控制的两个脚。初始化结束写设置速度的函数SetSpeed,参数给一个带符号的速度变量,负数表示反转,速度值定义为-100到100,

Motor.c:

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	PWM_Init();
}

void Motor_SetSpeed(int8_t Speed)
{
	if(Speed >= 0)
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_4);
		GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		PWM_SetCompare3(Speed);
	}
	else
	{
		GPIO_SetBits(GPIOA,GPIO_Pin_4);
		GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		PWM_SetCompare3(-Speed);
	}
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"

uint8_t KeyNum;
int8_t Speed;

int main(void)
{
	OLED_Init();
	Motor_Init();
	Key_Init();
	OLED_ShowString(1,1,"Speed:");
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Speed += 20;
			if(Speed > 100)
			{
				Speed = -100;
			}
		}
		Motor_SetSpeed(Speed);
		OLED_ShowSignedNum(1,7,Speed,3);
	}
}

TIM输入捕获

  • IC(Input Capture)输入捕获
  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式(PWM输入模式,专门为测量PWM频率和占空比设计),同时测量频率和占空比
  • 可配合主从触发模式,实现硬件全自动测量

频率测量

首先了解一下频率测量的方法:

输入捕获通道

上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿取一下CNT的值,自动存在CCR里,CCR捕获到的值就是计数值N,CNT的驱动时钟就是fc,fc/N就得到了待测信号的频率。还有一个细节每次捕获后需要把CNT清零,可以用主从触发模式自动完成,待会介绍。

主从触发模式

下面来看一下主从触发模式,主从触发模式有什么用?如何完成硬件自动化的操作呢?主从触发模式是主模式、从模式、触发源选择的简称。其中主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设,所以叫主模式。从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制,所以叫从模式。触发源选择就是选择从模式的触发源的,可以认为是从模式的一部分。触发源选择,选择指定的一个信号,得到TRGI,TRGI去触发从模式,从模式在列表里选择一项操作自动执行。

比如想让TI1FP1信号自动触发CNT清零,则触发源选择可以选择TI1FP1,从模式选择执行Reset操作。

输入捕获基本结构

PWMI基本结构

输入捕获模式应用-测频率

在PWM呼吸灯的基础上写。首先PWM模块,原来的逻辑是初始化TIM2的通道1,产生一个PWM波形,输出引脚是PA0,然后通过SetCompare1的函数调节CCR寄存器的值从而控制PWM的占空比,但是目前PWM的频率是初始化里写好了的是固定的,运行的时候调节不太方便,所以我们在最后再加一个函数,用来调节PWM频率。PWM频率=更新频率=72M/(PSC+1)/(ARR+1),所以PSC和ARR都可以调节频率,但是占空比=CCR/(ARR+1),所以通过调节ARR调节频率还会影响占空比,而通过PSC调节频率不会影响占空比,显然比较方便。所以我们的计划是固定ARR为100-1,通过调节PSC改变PWM频率,另外ARR为100-1,CCR的数值直接就是占空比用起来比较直观。我们后面在写一个函数在初始化之后单独修改PSC,在里面调用库函数里单独写入PSC的函数,TIM_PrescalerConfig()就是单独写入PSC的函数。

PWM.c:

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM2);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	/* 初始化输出比较单元 */
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较极性
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
	TIM_OCInitStructure.TIM_Pulse = 50;//设置CCR寄存器值
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	
	/* 启动定时器 */
	TIM_Cmd(TIM2,ENABLE);
}

void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2,Compare);
}

void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);
	
}

接下来写输入捕获的代码,还是建一个模块IC。在IC.c的IC_Init()函数里写输入捕获初始化。

初始化步骤按输入捕获基本结构来:

先介绍几个函数:

/*
* 根据TIM_ICInitStruct中指定的参数初始化TIM外设。
* 参数1 TIMx:其中x除6、7外可选1 ~ 17,用于选择TIM外设。
* 参数2 TIM_ICInitStruct:指向TIM_ICInitTypeDef结构体的指针包含指定TIM外设的配置信息。
* 返回值:无
*/
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
/*
* 根据TIM_ICInitStruct中指定的参数来测量外部PWM信号配置TIM外设。
* 参数1 TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设。
* 参数2 TIM_ICInitStruct:指向TIM_ICInitTypeDef结构体的指针包含指定TIM外设的配置信息。
* 返回值:无
*/
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
/*
* 用默认值填充每个TIM_ICInitStruct成员。
* 参数 TIM_ICInitStruct:指向被初始化的TIM_ICInitTypeDef结构体的指针。
* 返回值:无
*/
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
/*
* 选择输入触发器源TRGI
* 参数1 TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设。
* 参数2 TIM_InputTriggerSource:输入触发器源。选择从模式触发源
* 返回值:无
*/
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
/*
* 选择TIMx触发器输出模式。选择输出触发源TRGO。选择主模式输出触发源
* 参数1 TIMx:其中x为1、2、3、4、5、6、7、8、9、12或15,用于选择TIM外设。
* 参数2 TIM_TRGOSource:触发器输出源。
* 返回值:无
*/
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);
/*
* 选择TIMx从模式。从模式选择
* 参数1 TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设。
* 参数2 TIM_SlaveMode:定时器从模式。
* 返回值:无
*/
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
/*
* 分别读取4个通道的CCR
*/
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

开启时钟、配置GPIO、配置时基单元不在细说。我们需要TIM2输出PWM,所以输入捕获的定时器我们就换一个,换TIM3,之后GPIO的时钟就查引脚定义表,我们用TIM3的通道1,对应PA6。时基单元定时器TIM3,ARR自动从装值根据前文分析最好设置大一点防止计数溢出,我们就给最大值65536-1。预分频器决定了测周法的标准频率fc,暂时给72-1,这样标准频率就是72M/72=1MHz,方便计算,计数器采用向上计数。

/* 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
	
/* 选择时基单元时钟 */
TIM_InternalClockConfig(TIM3);
	
/* 配置时基单元 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//周期,ARR自动重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);

第四步,初始化输入捕获单元。利用TIM_ICInit()函数,第一个参数TIM3,第二个参数就结构体,有五个成员:

  • TIM_Channel, 配置哪个通道
  • TIM_ICFilter,选择输入捕获的滤波器
  • TIM_ICPolarity,极性,选择上升沿触发还是下降沿触发
  • TIM_ICPrescaler,分频器
  • TIM_ICSelection,选择触发信号从哪个引脚输入
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置哪个通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,选择上升沿触发还是下降沿触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器,不分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入,选择直连通道
TIM_ICInit(TIM3,&TIM_ICInitStructure);

第五步,配置TRGI的触发源为TI1FP1。

/* 触发源选择 */
TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);

第六步,配置从模式为Reset。

/* 配置从模式为Reset */
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);

最后,启动定时器。

TIM_Cmd(TIM3,ENABLE);

这样整个电路的配置就完成了,启动定时器之后,CNT就会在内部时钟的驱动下不断自增,即使信号没有过来,他也会不断自增不过这也没关系,因为有信号来的时候,他就会在从模式下自动清零,并不会影响测量。那初始化之后整个电路就能全自动测量了,当我们想要查看频率时,需要读取CCR,进行计算。所以我们再写一个函数返回最新周期的频率值fx=fc/N。1M/CCR的值,+1为了消除正负1误差。

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

PWM模块已经将待测信号输出到PA0,PA0又通过导线,输出到PA6,PA6是TIM3的通道1,通道1通过输入捕获模块,测量得到频率,然后在主循环里不断刷新显示频率。完整代码:

IC.c:

#include "stm32f10x.h"                  // Device header

void IC_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM3);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
    /* 配置输入捕获 */
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置哪个通道
	TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,选择上升沿触发还是下降沿触发
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	/* 触发源选择 */
	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
	
	/* 配置从模式为Reset */
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
	
	TIM_Cmd(TIM3,ENABLE);
}

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1,1,"Freq:00000Hz");
	
	
	PWM_SetPrescaler(720-1); //Freq = 72M / (PSC+1) / (ARR+1)
	PWM_SetCompare1(50);
	
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);
	}
}

PWMI模式测频率占空比

在上一个代码基础上,前三步不需要更改。输入捕获初始化部分需要进行升级,配置成两个通道同时捕获同一个引脚的模式。

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置哪个通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,选择上升沿触发还是下降沿触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入
//	TIM_ICInit(TIM3,&TIM_ICInitStructure);
//	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//配置哪个通道
//	TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
//	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;//极性,选择上升沿触发还是下降沿触发
//	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
//	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;//选择触发信号从哪个引脚输入
//	TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//上面代码的简化,该函数自动配置另一个通道为相反
//比如传入通道1直连上升沿,那函数里就会顺带配置通道2交叉下降沿
//可以快捷的把电路配置成PWMI模式的标准结构

最后其他也不用改,在写一个获取占空比的函数。根据前文分析,高电平的数值存在CCR2里,整个周期的计数值存在CCR1里,我们用CCR2/CCR1就得到了占空比。显示整数扩大100倍,对应0%到100%。

uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1)*100 / (TIM_GetCapture1(TIM3) + 1);//0% - 100%
}

 IC.c:

#include "stm32f10x.h"                  // Device header

void IC_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 选择时基单元时钟 */
	TIM_InternalClockConfig(TIM3);
	
	/* 配置时基单元 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式(向上计数、向下计数、中央对齐)
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//周期,ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器才有,给0就好
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	/* 配置输入捕获 */
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置哪个通道
	TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性,选择上升沿触发还是下降沿触发
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入
//	TIM_ICInit(TIM3,&TIM_ICInitStructure);
//	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//配置哪个通道
//	TIM_ICInitStructure.TIM_ICFilter = 0xF;//选择输入捕获的滤波器
//	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;//极性,选择上升沿触发还是下降沿触发
//	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
//	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;//选择触发信号从哪个引脚输入
//	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//上面代码的简化,该函数自动配置另一个通道为相反
	//比如传入通道1直连上升沿,那函数里就会顺带配置通道2交叉下降沿
	//可以快捷的把电路配置成PWMI模式的标准结构
	
	/* 触发源选择 */
	TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
	
	/* 配置从模式为Reset */
	TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
	
	TIM_Cmd(TIM3,ENABLE);
}

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1)*100 / (TIM_GetCapture1(TIM3) + 1);//0% - 100%
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	
	OLED_ShowString(1,1,"Freq:00000Hz");
	OLED_ShowString(2,1,"Duty:00%");
	
	
	PWM_SetPrescaler(720-1); //Freq = 72M / (PSC+1) / (ARR+1)
	PWM_SetCompare1(50);
	
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);
		OLED_ShowNum(2,6,IC_GetDuty(),2);
	}
}

TIM编码器接口

  • Encoder Interface 编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有1个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2

正交编码器

设计逻辑:首先把A向和B向的所有边沿作为计数器的计时时钟,出现边沿信号时,就计数器自增自减,增还是减由另一向的状态来确定,当出现某个边沿时,我们判断另一向的高低电平,如果对应另一向的状态出现在上面那个表那就是正转计数自增,反之出现在下面就是反转计数自减。 

编码器接口基本结构

工作模式

实例

(TI1TI2均不反相)

(TI1反相)

编码器接口应用-编码器接口测速

我们计划用TIM3接编码器,查表PA6和PA7是TIM3的通道1和通道2,所以需要编码器的A、B相接在PA6和PA7这两个引脚 

根据编码器结构图初始化:

电路初始化完成后,CNT就会随着旋转编码器旋转而自增自减。如果需要知道编码器位置,那直接读出CNT的值就好了。如果想要测编码器的速度和方向,那就需要每隔一段固定的闸门时间取出一次CNT,然后把CNT清零,这样就是测频法测速度了。

先看库函数:

/*
* 配置TIMx编码器接口。
* 参数1 TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设。
* 参数2 TIM_EncoderMode: TIMx编码器模式。
* 参数3 TIM_IC1Polarity: IC1极性
* 参数4 TIM_IC2Polarity: IC2极性
* 返回值:无
*/
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
                                uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);

 新建Encoder模块,第一步开启时钟和第二步GPIO配置时介绍一下上拉下拉的选择原则:一般看接在引脚的外部模块输出的默认1电平,如果外部模块空闲默认输出的高电平,我们就选择上拉输入,默认输入高电平,如果外部模块默认输出低电平我们选择下拉。和外部模块默认保持一致,防止默认电平打架,一般上拉输入比较多。如果不确定外部模块的默认状态或者外部信号输出功率非常小1就尽量选择浮空输入。浮空输入没有上拉电阻和下拉电阻去影响外部信号,缺点就是引脚悬空时没有默认电平了,输入就会受噪声干扰来回不断跳变。我们选择上拉输入

定时器内部时钟控制不需要,因为编码器接口会托管时钟,编码器接口就是一个带方向控制的外部时钟,所以内部时钟就没有用了。

第三步,时基单元配置,计数器模式这个参数目前也是没有作用的,因为计数方向也是被编码器接口托管的,自动重装值目前给65536-1,也就是满量程计数,这样计数范围最大而且方便换算为负数,预分频器给1-1,预分频器不分频。编码器的时钟直接驱动计数器。

第四步输入捕获单元配置。编码器只使用了通道1和通道2的滤波器和极性选择,所以我们只需要配置这两个参数。

/* 第一步RCC开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* 第二步GPIO初始化 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
		
/* 第三步时基单元初始化 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		//PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
/* 第四步配置输入捕获单元 */
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);//结构体赋默认值再部分修改需要的参数
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);//通道1
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);//通道2

第五步配置编码器接口,只需要调用一个函数就好了。第一个参数给TIM3,编码器模式选择选择TI1和TI2都计数,IC1的极性和IC2都不反相(Rising)。第六步开启时钟。

/* 第五步配置编码器接口 */
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	
/* 第六步启动定时器 */
TIM_Cmd(TIM3, ENABLE);

让0再减变为-1,使用int6_t就好了。利用临时变量Temp先存一下CNT值,再清零CNT。 

int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}

完整代码:

Encoder.c:

#include "stm32f10x.h"                  // Device header

void Encoder_Init(void)
{
	/* 第一步RCC开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	/* 第二步GPIO初始化 */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
		
	/* 第三步时基单元初始化 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	/* 第四步配置输入捕获单元 */
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICStructInit(&TIM_ICInitStructure);//结构体赋默认值再部分修改需要的参数
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);//通道1
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);//通道2
	
	/* 第五步配置编码器接口 */
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	
	/* 第六步启动定时器 */
	TIM_Cmd(TIM3, ENABLE);
}

int16_t Encoder_Get(void)
{
	int16_t Temp;
	Temp = TIM_GetCounter(TIM3);
	TIM_SetCounter(TIM3, 0);
	return Temp;
}

main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"

int16_t Speed;

int main(void)
{
	OLED_Init();
	Timer_Init();
	Encoder_Init();
	
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		OLED_ShowSignedNum(1,7,Speed,5);//主循环刷新显示Speed
	}
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
		Speed = Encoder_Get();//每隔1s读取一下速度
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值