Loading

STM32学习记录(六):定时器TIM

TIM是Timer的简写,是STM32的一种外设

定时器分为高级定时器、通用定时器、基本定时器。

通用定时器框图

这里只说明通用定时器的框图:

  • 从图中可以看出使用内部时钟CK_INT作为定时器时钟输入,CK_PSC是输入预分频器PSC的时钟,CK_PSC经过PSC分频后得到定时器计数用的时钟CK_CNT;
  • Auto-reload register(ARR)自动重装寄存器存放重装值,当CNT counter的值等于ARR的值,就会产生一个Update Interrupt,并将CNT counter的值重新设为0
  • 图中TIMx_CH1是定时器的通道1,通道1有输入和输出模式。在输出模式下,可以选择内部时钟CK_INT作为输入时钟,也可以选择外部时钟ETR作为输入。对于STMF103C8T6芯片的PA0口默认复用功能为TIM2_ETR输入以及TIM2_CH1,因此使用TIM2_CH1作为输出时,只能选择内部时钟CK_INT作为输入
  • OC1REF是CCR1(Capture/Compare Register 1)输出的信号,经过输出控制器Output control(可以对OC1REF进行翻转),最后输出到TIMx_CH1。

内部时钟计数模式

通用定时器的计数模式分向上计数、向下计数、向上向下计数模式三种。其中向上计数模式的时序图如下图所示,CK_CNT是CK_PSC分频后的时钟,这个时序图中分频系数为1,即fCK_CNT=fCK_PSC。假设自动重装值寄存器TIMx_ARR=0x36,当计数寄存器的值达到0x36等于TIMx_ARR中的值,这时会产生一个计数溢出脉冲(Counter overflow)和更新事件脉冲(Update event),计数寄存器重新从0开始计数,同时UIF标志位为1。

要实现TIM定时1s,Counter overflow的时钟频率就应该为1Hz。Counter Overflow的频率: $$ f_{overflow}= \frac{f_{PSC}}{PSC[15:0]+1} *\frac{1}{ARR[15:0]+1} $$,$ f_{PSC}$ 的取值取决于选择内部时钟还是外部时钟,选择内部时钟作为定时器时钟源时,\(f_{PSC}=f_{CKINT}\)。令$ PSC=7200-1、ARR=10000-1$ 、$ f_{CKINT}=72MHz $,可得 $ f_{overflow}=1Hz $,即时钟周期为1s。其中PSC[15:0]是预分频寄存器中的值,ARR[15:0]是自动重装寄存器的值。将CK_PSC四分频为CK_CNT的时序图如下图所示,预分频寄存器的值写入3,也就是4分频,这也是为什么计算频率时PSC的值要+1。由于计数器寄存器(Counter register)是从0开始计数,如果要计数m个脉冲,ARR[15:0]的值应该设置为m-1。

参考手册上有关预分频器寄存器的部分也说明了计数的时钟频率:\(f_{CK\_CNT}=\frac{f_{CKPSC}}{PSC[15:0]+1}\)

内部时钟定时1秒

定时器向上计数的模式、计数时间1s,每秒钟向串口发送数据

定时器初始化

void Timer_Init()
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_InternalClockConfig(TIM2);

	/**
	 * 时基单元初始化:
	 * CK_INT不分频、向上计数模式、ARR自动重载值9999、预分频系数7199、重复计数器=0(高级定时器才有)
     * 每过1s产生一次更新中断
	 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;		
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

	/* TIM2中断源配置为更新中断 */
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	/* NVIC配置 */
	NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* 打开定时器 */
	TIM_Cmd(TIM2, ENABLE);
}

定时器中断处理函数

USART重定向有关的内容参照另一篇博客STM32学习记录(五)之串口通信

/* 定时器2中端处理函数 */
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
		num++;
        /* 向USART1发送num */
        printf("%d", num);	
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);	//清除TIM2更新事件的中断标志位
	}
}

演示结果

定时器TIM2每秒产生一次更新中断(Update Interrupt),在中断处理函数中使用串口经过USB转串口向电脑发送数据

外部时钟计数模式

计数器时钟可以是以下的时钟源:

  • 内部时钟(CK_INT)
  • 外部时钟模式1:外部输入引脚 external input pin(TIx)
  • 外部时钟模式2:外部触发器输入external trigger input (ETR)
  • 内部触发器输入(ITRx):使用一个定时器作为令一个定时器的预分频器

外部触发器输入框图如下所示,使用ETR引脚,这也就是外部时钟模式2

参考手册上举了一个例子:计数器每2个ETR上升沿计数一次。ETR是外部触发器输入,ETRP则是经过分频后的信号,如下图ETRP是ETR二分频后的信号。ETRF是ETRP经过滤波后的信号,CK_INT作为滤波器的时钟信号,这个例子中没有滤波。Counter clock在经过2个ETR上升沿(ETRP由高变到低)后,在下一个CK_INT的上升沿时,产生一次脉冲。ETR上升沿与计数器实际时钟之间的延迟是由于ETRP信号上的再同步电路

外部时钟脉冲计数

使用外部时钟作为时钟输入,每个ETR上升沿计数一次,每记录10个ETR上升沿,产生一次更新中断(update interrupt),在更新中断中处理想要的结果。如果要每2个ETR上升沿计数一次,只需修改寄存器TIMx_SMCR的ETPS[1:0]位的值。

定时器初始化

void Timer_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB2Periph_GPIOA, ENABLE);
	
    /* 配置GPIO */
    GPIO_InitTypeDef GPIO_InitStructure;

	/*	配置ETR对应的GPIO */
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/**
	 * 使用外部时钟模式2:不分频、极性不翻转、触发滤波器值最大0X0F
	 */
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0X0F);


	/**
	 * 时基单元初始化:
	 * CK_INT不分频、向上计数模式、ARR自动重载值10、预分频系数1、重复计数器=0(高级定时器才有)
	 * TIM_ClockDivision对CK_INT进行分频,分频后的时钟用于ETR,TIx的数字滤波器
	 * 	
	 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

	/* TIM2中断配置 */
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
	/* NVIC配置 */
	NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* 打开定时器 */
	TIM_Cmd(TIM2, ENABLE);
}

PWM模式

TIM的Channel作为输出时,CCRx执行的操作是比较操作(与计数器的值比较)。

定时器TIM有两种PWM模式:

  • PWM模式1:向上计数时,计数寄存器TIMx_CNT的当前值小于比较寄存器TIMx_CCR1的值,OCxREF输出'1';当TIMx_CNT >= TIMx_CCR1的值,OCxREF输出'0'
  • PWM模式2:向上计数时,与PWM模式1正好相反

引用参考手册上的原文说明两种PWM模式:

PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT < TIMx_CCR1 else active (OC1REF=1). In downcounting, channel 1 is inactive (OC1REF=‘0) as long as TIMx_CNT>TIMx_CCR1 else active (OC1REF=1).

PWM mode 2 - In upcounting, channel 1 is inactive as long as TIMx_CNTTIMx_CCR1 else inactive. In downcounting, channel 1 is active as long as TIMx_CNT>TIMx_CCR1 else inactive.

向上计数的PWM模式1的时序图:

可以看到,CCRx=4时,TIMx_CNT < 捕获比较寄存器CCRx时,OCxREF输出'1';当TIMx_CNT >= 捕获比较寄存器CCRx时,OCxREF输CCRx时,OCxREF输出'0',当CCRx为其他值时,原理一样

PWM模式实现呼吸灯

使用TIM的PWM模式1实现LED呼吸灯的效果。

由上面的向上计数的PWM模式1的时序图以及脉冲宽度调制(Pulse width modulation: PWM)的原理可知,要达到呼吸灯的效果,只需改变每个周期的占空比。在TIM的PWM模式下,通过改变定时器TIM中捕获比较寄存器CCRx的值,来输出不同占空比的矩形波,每个矩形波高低电平维持时间是不一样的,以此来实现慢慢熄灭以及慢慢点亮的效果。可以看到通过修改CCR1的值,使得OC1REF的占空比不同;占空比不同,那么每次输出电平的高低的持续时间就不同。如果MCU的IO引脚输出低电平能点亮LED灯,下面的时序图表现出来的效果就是LED灯由亮--->灭。(这里输出的OC1REF没有Output control翻转,随着CCR1的值变大,低电平的持续时间变少)

定时器初始化

TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;设置ARR寄存器的值

TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; 设置PSC寄存器的值

设置TIM_Period和TIM_Prescaler是为了得到计数时钟CK_CNT,\(f_{CK\_CNT}\)=10KHz。

void Timer_Init(void)
{
	/* 开启时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB2Periph_GPIOA, ENABLE);
	
    /* 配置GPIO */
    GPIO_InitTypeDef GPIO_InitStructure;

	/*	配置TIM2_CH1对应的GPIO */
	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);

	/**
	 * 时基单元初始化:
	 * CK_INT不分频、向上计数模式、ARR自动重载值100、预分频系数72、重复计数器=0(高级定时器才有)
	 * TIM_ClockDivision对CK_INT进行分频,分频后的时钟用于ETR,TIx的数字滤波器
	 * 	
	 */
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//输出10000Hz的方波
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    /* 因为有些结构体成员对TIM2无效,所以要为TIM_OCInitStructure每个成员设置默认值 */
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);

    /**
	 * 定时器设置为PWM模式1、TIM_Pulse即CCRx寄存器的值、极性不翻转
	 * 
	 */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);

	/* 打开定时器 */
	TIM_Cmd(TIM2, ENABLE);
}

修改CCR1的值

TIM_SetCompare1函数用于修改CCR1寄存器里面的值。时序图见上

void Timer_Init(void);

int main(void)
{
	/* LED初始化,LED使用PA0 */
	LED_Init();
    
    /* 定时器初始化 */
	Timer_Init();
    
	while (1)
	{
        /* LED逐渐变暗 */
		for(int i = 0; i < 100; i++)	//i为100,因为计数周期就是100
		{
			TIM_SetCompare1(TIM2, i);	//修改捕获/比较寄存器的值
			delay_ms(10);				//添加延时,能够观察到呼吸灯效果
		}
        /* LED逐渐变亮 */
		for(int i = 99; i >= 0; i--)
		{
			TIM_SetCompare1(TIM2, i);
			delay_ms(10);
		}
	}
}

演示结果

参考资料

【STM32】第16集 动画告诉你, STM32的定时器到底怎么回事

[6-4] PWM驱动LED呼吸灯&PWM驱动舵机&PWM驱动直流电机_哔哩哔哩_bilibili

《STM32F103xx固件函数库用户手册》

《STM32F10xxx Reference manual》

posted @ 2024-07-23 11:08  记录学习的Lyx  阅读(538)  评论(0编辑  收藏  举报