STM32学习笔记(12)——定时器初步应用(2)

前续:STM32学习笔记(11)——定时器初步应用(1)

三、高级定时器——PWM互补输出

【实现功能】通过使用高级定时器 TIM1 的输出通道 CH1,输出一对互补信号 PWM。要求占空比为 50%。

【基本思路】查看原理图,在 STM32F103ZE 中,高级定时器 TIM1 的输出通道 1 对应的引脚是 PA8,互补输出通道 1 对应的引脚是 PB13,刹车通道对应的引脚是 PB12。只需初始化这三个引脚即可。

还是跟以前一样,我们设置分频因子为 72,则得到了一次计数周期为 1us,再设置重装载值为 1000,则计数器数到 1000 会重新清零,此时过去了 1000 * 1us = 1000us = 1ms。于是我们得到了 1ms 为周期的方波信号,不过此时还未设置占空比。

现在我们已经设置重装载值为 1000(对应的寄存器是ARR),为了使占空比为 50%,则我们要将脉冲宽度设置为 500(对应的寄存器是CCR1),因为 500/1000 = 50%。如果我们需要将占空比调整至 30%,则寄存器 CCR1 的值应该设置为 300。

在配置互补输出信号的时候,将两者电平极性设置为高电平,这样我们之前设置的占空比才是对应高电平的;如果设置的是低电平的话,那么占空比对应的就是低电平了。

解释一下空闲电平:如果中途关闭了输出使能,那么此时输出通道输出的就是空闲电平。空闲电平极性为高,则一直输出高电平;空闲电平极性为低,则一直输出低电平。

最后提一句,用通用定时器一样也能实现PWM互补输出。

【编程要点】

  • 输出通道、互补输出通道、刹车通道 GPIO 引脚初始化;
  • TIM1 时基单元初始化配置;
  • TIM1 输出比较通道、互补输出比较通道、刹车通道初始化配置;
  • main 函数调用 TIM 的初始化函数;
  • 通过 keil5 仿真逻辑仪查看实验结果。

完整代码如下:

1. advance_tim.h

#ifndef __ADVANCE_TIM_H
#define __ADVANCE_TIM_H

#include "stm32f10x.h"

#define ADVANCE_TIM					TIM1

#define ADVANCE_TIM_CLK				RCC_APB2Periph_TIM1
#define ADVANCE_TIM_PERIOD			(1000 - 1)  // 重装载值:1000us = 1ms
#define ADVANCE_TIM_PSC				(72 - 1)  	// 分频:72,得到周期:1us
#define ADVANCE_TIM_CCR1			500			// 脉冲宽度:500us
#define ADVANCE_TIM_RECNT			0

#define ADVANCE_TIM_CH1_PORT_CLK	RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT		GPIOA
#define ADVANCE_TIM_CH1_PIN			GPIO_Pin_8

#define ADVANCE_TIM_CH1N_PORT_CLK	RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT		GPIOB
#define ADVANCE_TIM_CH1N_PIN		GPIO_Pin_13

#define ADVANCE_TIM_BKIN_PORT_CLK	RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_BKIN_PORT		GPIOB
#define ADVANCE_TIM_BKIN_PIN		GPIO_Pin_12
#define ADVANCE_TIM_DEAD_TIME		11          // 配置死区时间为152ns,计算方法参见STM32中文手册

void ADVANCE_TIM_Init(void);

#endif /* __ADVANCE_TIM_H */

2. advance_tim.c

#include "advance_tim.h"

static void ADVANCE_TIM_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStrutcure;
	
	/* 输出比较通道初始化(PA8) */
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_PORT_CLK, ENABLE);
	GPIO_InitStrutcure.GPIO_Pin 	= ADVANCE_TIM_CH1_PIN;
	GPIO_InitStrutcure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStrutcure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(ADVANCE_TIM_CH1_PORT ,&GPIO_InitStrutcure);

	/* 输出比较互补通道初始化(PB13) */
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1N_PORT_CLK, ENABLE);
	GPIO_InitStrutcure.GPIO_Pin 	= ADVANCE_TIM_CH1N_PIN;
	GPIO_InitStrutcure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStrutcure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(ADVANCE_TIM_CH1N_PORT ,&GPIO_InitStrutcure);
	
	/* 输出比较刹车通道初始化(PB12) */
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_BKIN_PORT_CLK, ENABLE);
	GPIO_InitStrutcure.GPIO_Pin 	= ADVANCE_TIM_BKIN_PIN;
	GPIO_InitStrutcure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStrutcure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(ADVANCE_TIM_BKIN_PORT ,&GPIO_InitStrutcure);

	/* BKIN引脚默认输出低电平 */
	GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT, ADVANCE_TIM_BKIN_PIN);
}

static void ADVANCE_TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef 		TIM_OCInitStructure;
	TIM_BDTRInitTypeDef 	TIM_BDTRInitStructure;
	
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_CLK, ENABLE);
	
	/* 时基单元初始化 */
	TIM_TimeBaseStructure.TIM_Prescaler 		= ADVANCE_TIM_PSC; 	   	// 计数器时钟分频
	TIM_TimeBaseStructure.TIM_CounterMode 		= TIM_CounterMode_Up; 	// 计数模式
	TIM_TimeBaseStructure.TIM_Period 			= ADVANCE_TIM_PERIOD; 	// 自动重装载寄存器
	TIM_TimeBaseStructure.TIM_ClockDivision 	= TIM_CKD_DIV1; 		// 时钟分频因子(再次分频)
	TIM_TimeBaseStructure.TIM_RepetitionCounter = ADVANCE_TIM_RECNT; 	// 重复计数器
	TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);              // 使能时基单元
	
	/* 输出比较初始化 */
	TIM_OCInitStructure.TIM_OCMode 			= TIM_OCMode_PWM1; 			// 配置为PWM模式1
	TIM_OCInitStructure.TIM_OutputState 	= TIM_OutputState_Enable; 	// 输出使能
	TIM_OCInitStructure.TIM_OutputNState 	= TIM_OutputNState_Enable; 	// 互补输出使能
	TIM_OCInitStructure.TIM_Pulse 			= ADVANCE_TIM_CCR1; 		// 设置脉冲宽度
	TIM_OCInitStructure.TIM_OCPolarity 		= TIM_OCPolarity_High;		// 输出通道电平极性配置
	TIM_OCInitStructure.TIM_OCNPolarity 	= TIM_OCNPolarity_High;		// 互补输出通道电平极性配置
	TIM_OCInitStructure.TIM_OCIdleState 	= TIM_OCIdleState_Set;		// 输出通道空闲电平极性配置
	TIM_OCInitStructure.TIM_OCNIdleState 	= TIM_OCNIdleState_Reset;	// 互补输出通道空闲电平极性配置
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);                            // 开启TIM1定时器输出比较功能
	TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);            // 使用影子寄存器
	
	/* 刹车死区初始化 */
	TIM_BDTRInitStructure.TIM_OSSRState 		= TIM_OSSRState_Enable;     // 运行模式下“关闭状态”选择
	TIM_BDTRInitStructure.TIM_OSSIState 		= TIM_OSSIState_Enable;     // 空闲模式下“关闭状态”选择
	TIM_BDTRInitStructure.TIM_LOCKLevel 		= TIM_LOCKLevel_1;          // 锁定设置
	TIM_BDTRInitStructure.TIM_DeadTime 			= ADVANCE_TIM_DEAD_TIME;    // 配置死区时间为152ns
	TIM_BDTRInitStructure.TIM_Break 			= TIM_Break_Enable;         // 刹车通道使能
	TIM_BDTRInitStructure.TIM_BreakPolarity 	= TIM_BreakPolarity_High;   // 刹车通道电平极性为高电平
	TIM_BDTRInitStructure.TIM_AutomaticOutput 	= TIM_AutomaticOutput_Enable; // 自动输出使能
	TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);                    // 使能刹车功能
	
	/* 使能定时器 */
	TIM_Cmd(ADVANCE_TIM, ENABLE);
	/* 定时器主输出使能,通用定时器不需要这一句 */
	TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE); 
}

void ADVANCE_TIM_Init(void)
{
	ADVANCE_TIM_GPIO_Config();
	ADVANCE_TIM_Mode_Config();
}

3. main.c

#include "stm32f10x.h"
#include "advance_tim.h"

int main(void)
{	
	ADVANCE_TIM_Init(); // 只需调用这个函数,STM32就会输出一对互补信号PWM
	while(1)
	{

	}
}

4. Keil5 逻辑分析仪仿真

因为手头没有示波器,因此用软件自带逻辑分析仪进行仿真。这里只是大概说一下怎么设置,详细步骤请查看其它相关博文。

首先在魔法棒(options for target)的 debug 选项卡按照下图设置好(DARMSTM.DLL)(-pSTM32F103ZE):

在这里插入图片描述

如下图所示,菜单栏点击 Debug/Start Debug Session,然后点击图中1处的示波器,选择 Logic Analyzer。菜单栏点击 View/Symbols Window,然后右边会出现窗口,点击其中的 Virtual Registers,便可以查看哪些寄存器可以加入观察对象。

在这里插入图片描述

点击上图2处的setup,加入 PORTA 和 PORTB,并设置好显示类型、掩码和右移位数。比如我要查看 PA13,那么 Display Type 设置为 Bit,掩码为 0x100(二进制 0000 0001 0000 0000 = 十六进制 0x100),右移8位。如图所示:

在这里插入图片描述

点击 Run,等一会时间再点 Stop,即可查看仿真结果(基本符合预期):

在这里插入图片描述

注意,仿真结果并不代表实际结果也是这样。因为仿真的是理想状态。

附:呼吸灯的实现

现在我们来讲讲呼吸灯是如何实现的。如果不用定时器,用延时函数写呼吸灯的部分程序如下:

#include "stm32f10x.h"
#include "systick.h"
#include "led.h"

int main(void)
{	
	uint16_t i = 0;
	uint16_t num = 1500;
	LED_Init();
	while(1)
	{
		for(i = 0; i < num; i++)
		{
			LED0_ON;
			Systick_Delay_us(i); // 我自己用systick写的延时函数,可参考我以前的systick文章
			LED0_OFF;
			Systick_Delay_us(num - i);
		}
		for(i = num; i > 0; i--)
		{
			LED0_ON;
			Systick_Delay_us(i);
			LED0_OFF;
			Systick_Delay_us(num - i);
		}
	}
}

由以上程序可知呼吸灯的实现原理。其实现原理非常简单,就是利用人眼的延缓视觉效果,逐渐加快 LED 亮灭的频率,然后逐渐减慢 LED 亮灭的频率,其实质是一个高电平占空比不断变少、而后不断变多的方波信号,即可调制信号 PWM,这样就实现呼吸灯的效果了,如下图所示(借用了他人图片,若有侵权请联系本人进行删除):

在这里插入图片描述

类似的,如果使用定时器去实现,那么只需每次循环都修改占空比即可,这样就达到了不断修改占空比的功能,进而实现呼吸灯的目的。修改后的 main 函数:

#include "stm32f10x.h"
#include "systick.h"
#include "advance_tim.h"

int main(void)
{	
	uint16_t i;
	ADVANCE_TIM_Init();
	while(1)
	{
		for(i = 0; i < 1000; i++)
		{
			TIM_SetCompare1(TIM1, i); // 每次循环都重新设置CH1脉冲宽度(亮变暗)
			Systick_Delay_us(1500);
		}
		for(i = 999; i > 0; i--)
		{
			TIM_SetCompare1(TIM1, i); // 每次循环都重新设置CH1脉冲宽度(暗变亮)
			Systick_Delay_us(1500);
		}
		Systick_Delay_ms(500);
	}
}

需要注意的是,STM32的高级定时器 TIM1 的输出引脚与 LED 引脚并不连在一起,那我们怎么去实现呼吸灯呢?这里提供两个办法:

方法1:使用杜邦线和面包板外接 LED 灯

在这里插入图片描述

在这里插入图片描述

需要自行准备LED灯(注意区分正负极),自己连接电路,记得连上拉电阻,大概 100 - 200 欧姆即可。如果忘记连电阻,LED 会烧掉,几分钱也没了,不仅如此,还会伴随一股焦味,如果手没及时拿开还可能会肉疼一会儿。

这里只实现了一个呼吸灯。本来打算写个四路输出PWM(实现起来很简单,就是输出通道初始化4次即可,下面第四部分将展示4路输出是如何编写的),实现4个流水呼吸灯的小玩意,但是我的面包板似乎总是间歇性接触不良,导致进一步的调试极为麻烦,而且板载 LED 只有两个,因此不得不放弃。

方法2:使用杜邦线将输出引脚和 LED 引脚连起来,即飞线

在这里插入图片描述

这种办法比较便捷:一般的系统版上都会有排针,可以用两端都是母头的杜邦线将输出引脚(PA8)和 LED0(PB5)连起来,将互补输出引脚(PB13)和 LED1(PE5)连起来,你会看到两盏呼吸灯相互亮灭,这与仿真结果符合。

四、高级定时器——PWM输入捕获

这部分内容挺重要的,因为是最后压轴出场!!!

【实现功能】使用通用定时器输出4路不同占空比的PWM信号,然后使用高级定时器的输入捕获功能测量PWM输出信号的周期和占空比,并将结果通过USART输出到上位机上。

【基本思路】通用定时器的4路输出就不用讲了,思路与呼吸灯是一样的。下面我们来详细分析输入捕获是如何工作的,如图所示:

在这里插入图片描述

我们使用高级定时器 TIM1 的输入捕获功能来测量信号的周期和占空比,使用的是 TI1 通道,通过输入滤波器和边沿检测器后产生2路滤波信号 TI1FP1 和 TI1FP2,其中 TI1FP1 映射到了捕获通道 IC1,捕获到的值送入寄存器 CCR1;TI1FP2 映射到了捕获通道 IC2,捕获到的值送入到寄存器 CCR2。因此实际上 TIM1 使用了两路通道 CH1 和 CH2

我们需要理解信号 TI1FP1 和 TI1FP2 的实质:二者均来自于同一个通道 TI1,然后映射到不同的捕获通道,其实还是同一路信号,但是由于经过了边沿检测器,这两路信号的极性相反。当我们将 TI1FP1 配置为上升沿触发的时候,TI1FP2 就是下降沿触发;反之亦然。

既然有两路信号 TI1FP1 和 TI1FP2,那么用哪个通道判断 PWM 上升沿的到来?STM32提供的办法是:可以将其中一路信号设置为触发信号,告诉定时器,如果这路信号检测到上升沿,那么就可以开始捕获工作了。我们选择 TI1FP1 作为触发信号。

那么另外一路信号 TI1FP2 怎么办呢?STM32提供的办法是:将另一路信号 TI1FP2 设置为从机模式,这样另一路信号就会在主信号 TI1FP1 触发时也会被触发。这时的 TI1FP1 称之为从模式触发源。 从模式在 STM32 一共有四种:复位模式、门控模式、触发模式、外部时钟模式2 + 其他三种触发模式(说白了就是可以与其他模式搭配使用)。我们将 TI1FP2 配置为从模式的复位模式。现在我们只需要记住复位模式的功能:在发生一个触发输入事件时,计数器和它的预分频器能够重新被初始化。

读到这里的同学可能不明白上面这一通操作是干嘛的,我们举个例子就明白了:PWM 上升沿到来,TI1FP1 检测到上升沿(被触发了),由于 TI1FP2 是从机,因此也会被触发;由于是复位模式,计数器的值清零,此时不会产生捕获中断。下降沿到来时,TI1FP2 是下降沿触发,因此会 发生捕获事件,将此时计数器的值存到寄存器 CCR2。新一轮上升沿到来时,TI1FP1 是上升沿触发,因此发生捕获事件,将此时计数器的值存到寄存器 CCR1,此时产生捕获中断,同时计数器清零。因此我们测得的周期是 72MHz / CCR1,测得的占空比是 CCR2 / CCR1。但实际上,计数器是从0开始计数的,所以正确的结果是:周期 = 72MHz / (CCR1+1),占空比 = (CCR2+1) / (CCR1+1)。 整个过程如下图所示:

在这里插入图片描述

看到这里大家也许明白为什么要弄个从机模式了。因为测量占空比和测量周期的工作必须要同时开始进行,所以需要一个主通道和一个从通道,主通道说开始要做事了,从通道也会跟着同时做事!

到这里其实已经讲解的很清楚了,不过需要注意三点:

(1)因为只有 TI1FP1 和 TI2FP2 连到了从模式控制器,所以PWM输入模式只能使用 TIMx_CH1 和 TIMx_CH2 信号,用 CH3 和 CH4 是不行的哦,可以对照功能框图进行查看。

(2)注意直连(TIM_ICSelection_DirectTI)和非直连(TIM_ICSelection_IndirectTI)的区别:直连指的是输入通道 IC1 连接 CH1,输入通道 IC2 连接到 CH2;非直连指的是输入通道 IC1 连接 CH2,输入通道 IC2 连接到 CH1。对于我们这个实验来说,选择直连,IC1 测得就是周期,IC2 测的是占空比;选择非直连,IC1 测得就是占空比,IC2 测的是周期。

(3)不用配置 IC2(或者说TI1FP2),因为你给一个通道配置了时钟和极性后,另一个通道会通过硬件给你配置好。如果你多配置了 IC2,反而测量结果会不正确。

最后我们附上STM32中文参考手册里的 PWM 输入模式的配置步骤:

  • 两个ICx信号被映射至同一个TIx输入。
  • 这2个ICx信号为边沿有效,但是极性相反。
  • 其中一个TIxFP信号被作为触发输入信号,而从模式控制器被配置成复位模式。

例如,你需要测量输入到TI1上的PWM信号的长度(TIMx_CCR1寄存器)和占空比(TIMx_CCR2
寄存器),具体步骤如下(取决于CK_INT的频率和预分频器的值):

  • 选择TIMx_CCR1的有效输入:置TIMx_CCMR1寄存器的CC1S=01(选中TI1)。
  • 选择TI1FP1的有效极性(用来捕获数据到TIMx_CCR1中和清除计数器):置CC1P=0(上升沿
    有效)。
  • 选择TIMx_CCR2的有效输入:置TIMx_CCMR1寄存器的CC2S=10(选中TI1)。
  • 选择TI1FP2的有效极性(捕获数据到TIMx_CCR2):置CC2P=1(下降沿有效)。
  • 选择有效的触发输入信号:置TIMx_SMCR寄存器中的TS=101(选择TI1FP1)。
  • 配置从模式控制器为复位模式:置TIMx_SMCR中的SMS=100。
  • 使能捕获:置TIMx_CCER寄存器中CC1E=1且CC2E=1。

【编程要点】

  • 通用定时器时基单元初始化;
  • 通用定时器4个GPIO引脚初始化;
  • 通用定时器4路PWM输出CH1-CH4初始化;
  • 高级定时器时基单元初始化;
  • 高级定时器GPIO引脚初始化;
  • 高级定时器输入捕获IC1初始化(不用初始化IC2);
  • 开启高级定时器捕获中断,并配置NVIC;
  • 编写高级定时器中断服务函数,并计算测量结果,然后输出。

完整代码如下:

1. general_tim.h

#ifndef __GENERAL_TIM_H
#define __GENERAL_TIM_H

#include "stm32f10x.h"

/*******通用定时器TIMx输出比较******/
#define GENERAL_TIM  				TIM3
#define GENERAL_TIM_CLK             RCC_APB1Periph_TIM3
#define GENERAL_TIM_PERIOD			(100 - 1) 	// 100 * 1us = 100us, f = 1 / 10^(-4) = 10^4Hz = 10000Hz
#define GENERAL_TIM_PSC				(72 - 1) 	// 1us
#define GENERAL_TIM_RECNT			0

#define GENERAL_TIM_CH1_PORT_CLK	RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT		GPIOA
#define GENERAL_TIM_CH1_PIN			GPIO_Pin_6
#define GENERAL_TIM_CCR1			10			// 占空比:10%

#define GENERAL_TIM_CH2_PORT_CLK	RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH2_PORT		GPIOA
#define GENERAL_TIM_CH2_PIN			GPIO_Pin_7
#define GENERAL_TIM_CCR2			25			// 占空比:25%

#define GENERAL_TIM_CH3_PORT_CLK	RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH3_PORT		GPIOB
#define GENERAL_TIM_CH3_PIN			GPIO_Pin_0
#define GENERAL_TIM_CCR3			50			// 占空比:50%

#define GENERAL_TIM_CH4_PORT_CLK	RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH4_PORT		GPIOB
#define GENERAL_TIM_CH4_PIN			GPIO_Pin_1
#define GENERAL_TIM_CCR4			75			// 占空比:75%

void GENERAL_TIM_Init(void);

#endif /* __GENERAL_TIM_H */

2. general_tim.c

#include "general_tim.h"

/* 输出通道 GPIO初始化 */
static void GENERAL_TIM_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/* 输出通道CH1 GPIO初始化(PA6) */
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin 	= GENERAL_TIM_CH1_PIN;
	GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
	
	/* 输出通道CH2 GPIO初始化(PA7) */
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin 	= GENERAL_TIM_CH2_PIN;
	GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
	
	/* 输出通道CH3 GPIO初始化(PB0) */
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin 	= GENERAL_TIM_CH3_PIN;
	GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
	
	/* 输出通道CH4 GPIO初始化(PB1) */
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_PORT_CLK, ENABLE);
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin 	= GENERAL_TIM_CH4_PIN;
	GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}

/* 时基单元和输入捕获通道初始化 */
static void GENERAL_TIM_MODE_Config(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef		TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE);
	
	/* 时基单元结构体初始化 */
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode 	= TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Period 		= GENERAL_TIM_PERIOD;
	TIM_TimeBaseStructure.TIM_Prescaler 	= GENERAL_TIM_PSC;
	TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
	
	/* 输出比较通道结构体初始化 */
	TIM_OCInitStructure.TIM_OCMode 			= TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState 	= TIM_OutputState_Enable;
	//TIM_OCInitStructure.TIM_OutputNState 	= TIM_OutputNState_Disable; // 不使用互补输出,不用理会
	TIM_OCInitStructure.TIM_OCPolarity 		= TIM_OCPolarity_High;
	//TIM_OCInitStructure.TIM_OCNPolarity 	= TIM_OCNPolarity_High;		// 不使用互补输出,不用理会
	//TIM_OCInitStructure.TIM_OCIdleState 	= TIM_OCIdleState_Set;		// 不使用空闲电平,不用理会
	//TIM_OCInitStructure.TIM_OCNIdleState 	= TIM_OCNIdleState_Reset;	// 不使用互补输出,不用理会
	
	/* 输出通道CH1初始化 */
	TIM_OCInitStructure.TIM_Pulse = GENERAL_TIM_CCR1;
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	/* 输出通道CH2初始化 */
	TIM_OCInitStructure.TIM_Pulse = GENERAL_TIM_CCR2;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	/* 输出通道CH3初始化 */
	TIM_OCInitStructure.TIM_Pulse = GENERAL_TIM_CCR3;
	TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	/* 输出通道CH4初始化 */
	TIM_OCInitStructure.TIM_Pulse = GENERAL_TIM_CCR4;
	TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	/* 使能计数器 */
	TIM_Cmd(GENERAL_TIM, ENABLE);
}


void GENERAL_TIM_Init(void)
{
	GENERAL_TIM_GPIO_Config();
	GENERAL_TIM_MODE_Config();
}

3. advance_tim.h

#ifndef __ADVANCE_TIM_H
#define __ADVANCE_TIM_H

#include "stm32f10x.h"

#define ADVANCE_TIM					TIM1

#define ADVANCE_TIM_CLK				RCC_APB2Periph_TIM1
#define ADVANCE_TIM_PERIOD			(1000 - 1)  // 重装载值:1000us
#define ADVANCE_TIM_PSC				(72 - 1)  	// 分频:72,得到周期:1us
#define ADVANCE_TIM_RECNT			0

#define ADVANCE_TIM_CH1_PORT_CLK	RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT		GPIOA
#define ADVANCE_TIM_CH1_PIN			GPIO_Pin_8

#define ADVANCE_TIM_CH1N_PORT_CLK	RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT		GPIOB
#define ADVANCE_TIM_CH1N_PIN		GPIO_Pin_13

#define ADVANCE_TIM_BKIN_PORT_CLK	RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_BKIN_PORT		GPIOB
#define ADVANCE_TIM_BKIN_PIN		GPIO_Pin_12
#define ADVANCE_TIM_DEAD_TIME		11

#define ADVANCE_TIM_IRQ             TIM1_CC_IRQn
#define ADVANCE_TIM_IRQHandler      TIM1_CC_IRQHandler

void ADVANCE_TIM_Init(void);

#endif /* __ADVANCE_TIM_H */

4. advance_tim.c

#include "advance_tim.h"

static void ADVANCE_TIM_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStrutcure;
	
	/* 输入捕获通道初始化(PA8) */
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_PORT_CLK, ENABLE);
	GPIO_InitStrutcure.GPIO_Pin 	= ADVANCE_TIM_CH1_PIN;
	GPIO_InitStrutcure.GPIO_Mode 	= GPIO_Mode_IN_FLOATING;
	GPIO_InitStrutcure.GPIO_Speed 	= GPIO_Speed_50MHz;
	GPIO_Init(ADVANCE_TIM_CH1_PORT ,&GPIO_InitStrutcure);
}

static void ADVANCE_TIM_NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure; 

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);		
    NVIC_InitStructure.NVIC_IRQChannel 					 = ADVANCE_TIM_IRQ; 	
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority 		 = 3;	
    NVIC_InitStructure.NVIC_IRQChannelCmd 				 = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

static void ADVANCE_TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef 		TIM_ICInitStructure;
	
	RCC_APB2PeriphClockCmd(ADVANCE_TIM_CLK, ENABLE);
	
	/* 时基单元初始化 */
	TIM_TimeBaseStructure.TIM_Prescaler 		= ADVANCE_TIM_PSC; 	   	// 计数器时钟分频
	TIM_TimeBaseStructure.TIM_CounterMode 		= TIM_CounterMode_Up; 	// 计数模式
	TIM_TimeBaseStructure.TIM_Period 			= ADVANCE_TIM_PERIOD; 	// 自动重装载寄存器
	TIM_TimeBaseStructure.TIM_ClockDivision 	= TIM_CKD_DIV1; 		// 时钟分频因子
	TIM_TimeBaseStructure.TIM_RepetitionCounter = ADVANCE_TIM_RECNT; 	// 重复计数器
	TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);
	
	/* 输入捕获IC1初始化(测量周期) */
	TIM_ICInitStructure.TIM_Channel 	= TIM_Channel_1;			// 选择捕获通道1
	TIM_ICInitStructure.TIM_ICPolarity 	= TIM_ICPolarity_Rising;	// 上升沿捕获
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直连捕获
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			// 捕获分频因子为1,即不分频
	TIM_ICInitStructure.TIM_ICFilter 	= 0;						// 滤波系数,这里不进行滤波
	TIM_PWMIConfig(ADVANCE_TIM, &TIM_ICInitStructure);
	
	/* 不用初始化IC2 */

	/* 选择输入捕获的触发信号 */
	TIM_SelectInputTrigger(ADVANCE_TIM, TIM_TS_TI1FP1);	
	/* 使能主从模式 */
	TIM_SelectMasterSlaveMode(ADVANCE_TIM, TIM_MasterSlaveMode_Enable); 	
	/* 选择从模式: 复位模式 */
	TIM_SelectSlaveMode(ADVANCE_TIM, TIM_SlaveMode_Reset); 
	// 若输入模式是PWM1,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位
	
	/* 使能捕获中断 */
	TIM_ITConfig(ADVANCE_TIM, TIM_IT_CC1, ENABLE);	
	/* 清除中断标志位 */
	TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);
	/* 使能定时器 */
	TIM_Cmd(ADVANCE_TIM, ENABLE);
}

void ADVANCE_TIM_Init(void)
{
	ADVANCE_TIM_GPIO_Config();
	ADVANCE_TIM_NVIC_Config();
	ADVANCE_TIM_Mode_Config();
}

5. stm32f10x_it.c

#include "stm32f10x_it.h" 
#include "advance_tim.h"
#include "usart.h"

void ADVANCE_TIM_IRQHandler(void)
{
	volatile uint16_t IC1Value = 0; 
	volatile uint16_t IC2Value = 0;
	volatile float DutyCycle = 0;
	volatile float Frequency = 0;

	TIM_ClearITPendingBit(ADVANCE_TIM, TIM_IT_CC1);
	
	IC1Value = TIM_GetCapture1(ADVANCE_TIM); // 周期
	IC2Value = TIM_GetCapture2(ADVANCE_TIM); // 脉冲宽度
	
	if(IC1Value != 0)
	{
		DutyCycle = ( (float)(IC2Value+1) ) / (IC1Value+1); // 占空比=(IC2)/(IC1)
		Frequency = (float)( (72000000/(ADVANCE_TIM_PSC+1)) / (IC1Value+1) ); // 周期=(1*10^6)/(IC1)
		printf("占空比:%0.2f%%  周期:%0.2fHz\r\n", DutyCycle*100, Frequency);
	}
}

6. main.c

#include "stm32f10x.h"
#include "advance_tim.h"
#include "general_tim.h"
#include "usart.h"

int main(void)
{	
	USART_Config();
	printf("\r\n开始测试!\r\n");
	GENERAL_TIM_Init();
	ADVANCE_TIM_Init();
	while(1)
	{
		
	}
}

使用杜邦线可将输出通道和输入通道连接起来,就能在串口调试助手中看到正确结果。

长达数十天的定时器内容已经学习完毕了,完结撒花!

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
呼吸灯是一种常见的 LED 灯效,可以让 LED 灯逐渐从暗到亮再从亮到暗,循环往复。在 STM32F103ZE 上实现呼吸灯可以通过 PWM(脉冲宽度调制)信号来控制 LED 的亮度,具体步骤如下: 1. 配置 TIM(定时器)模块为 PWM 模式,设置 TIM 的时钟源和分频系数,设置 PWM 的周期和占空比; 2. 配置 GPIO(通用输入输出)模块,将 LED 引脚设置为输出模式; 3. 在主循环中循环改变 PWM 的占空比,实现 LED 的呼吸灯效果。 下面是一个简单的示例代码,实现了在 PB0 引脚上控制一个 LED 的呼吸灯效果: ```c #include "stm32f10x.h" #include "stm32f10x_tim.h" #include "stm32f10x_gpio.h" int main(void) { // TIM3 控制 PWM 信号 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_OCInitTypeDef TIM_OCInitStruct; GPIO_InitTypeDef GPIO_InitStruct; // 打开 TIM3 和 GPIOB 的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置 GPIOB.0 为 AF_PP 输出模式 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // 配置 TIM3 为 PWM 模式 TIM_TimeBaseInitStruct.TIM_Period = 1000 - 1; // PWM 周期为 1ms TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; // 时钟频率为 72MHz,分频系数为 72,得到 1MHz 的计数频率 TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct); TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStruct.TIM_Pulse = 500; // PWM 占空比为 50% TIM_OC2Init(TIM3, &TIM_OCInitStruct); TIM_Cmd(TIM3, ENABLE); // 启动计数器 while (1) { for (uint16_t i = 0; i <= 1000; i++) // 增加 PWM 占空比,实现 LED 逐渐变亮 { TIM_SetCompare2(TIM3, i); for (volatile uint32_t j = 0; j < 10000; j++); // 延时一段时间 } for (uint16_t i = 1000; i >= 0; i--) // 减小 PWM 占空比,实现 LED 逐渐变暗 { TIM_SetCompare2(TIM3, i); for (volatile uint32_t j = 0; j < 10000; j++); // 延时一段时间 } } } ``` 在上面的代码中,我们使用了 TIM3 定时器的 PWM 功能来控制 LED 的亮度。通过不断改变 PWM 的占空比,实现了 LED 的呼吸灯效果。注意,我们在循环中使用了一个简单的延时函数,这里仅为示例代码,实际项目中应该使用更加可靠和精确的延时方式。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值