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(TIM12);
	
/* 配置时基单元 */
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(TIM12);
	
/* 配置时基单元 */
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,

#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;	//控制A1和A2口驱动LED
	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);
	}
}

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;	//控制A1和A2口驱动LED
	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);
	}
}

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值