STM32外设地图-Timer

一、关键概念

1、定时功能:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,定时时间为 10MS。

  配置CK_CNT时钟来源为 内部时钟CK_INT,然后根据定时时间10MS,配置CK_CNT的预分频为 72 - 1(一个CK_CNT周期 1us),配置计数器溢出值为 10000 - 1(10MS溢出);配置计数器为Upcounting mode,循环模式,并使能Update中断。

  软件启动计数器后,每个CK_CNT脉冲驱动计数器加1,当计数达到设定的溢出值9999时(10MS定时到了),计数器Overflow 并触发Update中断(软件在中断函数中处理定时超时),然后计数器从0开始重新计数。

2、统计TIMx_ETR引脚 输入的脉冲个数
  配置CK_CNT时钟来源为 TIMx_ETR引脚,CK_CNT的预分频为1,计数器溢出值设置为最大65535。配置计数器为Upcounting mode,循环模式,并使能Update中断。

  软件启动计数器后,每个CK_CNT脉冲(即来自TIMx_ETR引脚的脉冲)驱动计数器加1,当计数达到设定的溢出值 65535时,计数器Overflow 并触发Update中断(在中断函数中记录计数器Overflow次数),然后计数器从0开始重新计数。软件读计数器当前计数值,并结合计数器Overflow次数,可计算出TIMx_ETR引脚输入的脉冲个数。

3、测量TIMx_CH1引脚 输入的PWM信号的频率和占空比:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,输入PWM的频率范围是 1Khz(period = 1MS,1%占空比时高电平周期为 10us) ~ 100Khz (period = 10us,1%占空比时高电平周期为 0.1us)。

  配置CK_CNT时钟来源为 内部时钟CK_INT,配置CK_CNT的预分频为 7 - 1(一个CK_CNT周期 ≈ 0.1us),配置计数器溢出值为 20000 - 1(2MS溢出)。配置计数器为Upcounting mode,循环模式,并使能Update中断。配置CC1 channel为输入 且输入引脚为TIMx_CH1,输入信号不分频、不滤波,输入信号的上升沿触发CC1捕获,并使能 CC1 interrupt;配置CC2 channel为输入 且输入引脚为 TIMx_CH1,输入信号不分频、不滤波,输入信号的下降沿触发CC2捕获。配置 Slave mode为 Reset mode,且 TIMx_CH1信号的上升沿触发 Counter reset。

  软件每次在需要计算PWM频率和占空比时,都要启动一次PWM测量功能(或者用一个定时器定期启动,这样PWM频率和占空比就能定期自动更新)。软件启动计数器 并使能 CC1、CC2 channel后:

  当 Counter overflow时,触发Update interrupt,在中断函数中设置 PWM频率为0,占空比为 0%(读TIMx_CH1引脚为低电平)或 100%(读TIMx_CH1引脚为高电平),最后停止计数器 并禁能CC1、CC2 channel;

  当TIMx_CH1产生下降沿时, 寄存器CCR2 捕获计数器当前计数值;

  当TIMx_CH1产生上升沿时, 寄存器CCR1 捕获计数器当前计数值,然后Counter reset,并触发CC1 interrupt,在中断函数中记录TIMx_CH1上升沿的次数,若次数为2(2次上升沿能保证CCR1是PWM周期,CCR2是一个PWM周期中高电平的时间),则设置PWM周期为(CCR1 * 0.1us),占空比为 CCR2 / CCR1,最后停止计数器 并禁能CC1、CC2 channel。

4、输出指定频率和占空比的PWM波形:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,TIMx_CH1引脚输出PWM 且频率为100Khz(period = 10us)、占空比为10%,TIMx_CH1引脚高电平有效。

  配置CK_CNT时钟来源为 内部时钟CK_INT,配置CK_CNT的预分频为 72 - 1(一个CK_CNT周期 为1us),配置计数器溢出值为 10 - 1(10us溢出)。配置计数器为Upcounting mode,循环模式。配置CC1 channel为输出 且 输出端高电平有效 且模式为 PWM mode1,计数器比较值CCR1为 1。

  软件启动计数器 并使能 CC1 channel后,每个CK_CNT脉冲驱动计数器加1,当计数达到设定的溢出值时,计数器Overflow 并从0开始重新计数,在这个过程中,定时器通过不断比较 计数器当前值 和 计数器比较值CCR1,在TIMx_CH1引脚输出一个频率为100Khz、占空比为10%的PWM波形。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、程序开发流程

1、TIM3定时功能

/* 函数功能:启动 timer3. 此接口供外部模块调用. */
void timer3_start(void)
{
	CEN bit = 1;
}

/* 函数功能:停止 timer3. 此接口供外部模块调用. */
void timer3_stop(void)
{
	CEN bit = 0;
}

/* 函数功能:设置timer3定时时间、超时回调函数, 以及超时后是否停止. 此接口供外部模块调用.
   返回值:0, 设置成功; 非0, 设置失败
*/
int timer3_set(uint16_t timeout_us, void (*cb)(void), bool one_shot_flag)
{
	timer3->cb = cb;
	TIM3_ARR = timeout_us - 1;	//一个CK_CNT周期 已配置为1us
	OPM bit = one_shot_flag == TRUE ? 1 : 0;
	TIM3_CNT = 0;
	return 0;
}

static void timer3_irq_handler(void)
{
	if(UIF标志置位)
	{
		清零UIF标志;
		if(timer3->cb)
		{
			timer3->cb();
		}
	}
}

static void timer3_init(void)
{
	ECE bit = 0, SMS[2:0] = 0;	 //配置CK_CNT时钟来源为 内部时钟CK_INT
	DIR bit = 0, CMS[1:0] = 0;	 //计数器为Upcounting mode
	TIM3_PSC = 72 - 1;		  	//一个CK_CNT周期为 1us (CK_INT频率为 72Mhz)
	UIE bit = 1;			    //使能Update interrupt
	
	在NVIC中设置timer3中断优先级并使能中断;
}            

2、统计TIM3_ETR引脚输入的脉冲个数

static uint32_t g_overflow_cnt; 

/* 函数功能:读 TIM3_ETR引脚输入的脉冲个数. 此接口供外部模块调用 */
uint32_t timer3_ETR_pulse_read(void)
{
	uint32_t cnt = g_overflow_cnt * (TIM3_ARR + 1) + (TIM3_CNT + 1);
	
	interrupt_disable();
	g_overflow_cnt = 0;
	interrupt_enable();
	
	TIM3_CNT = 0;
	return cnt;
}

static void timer3_irq_handler(void)
{
	if(UIF标志置位)
	{
		清零UIF标志;
		(g_overflow_cnt)++;
	}
}

static void timer3_init(void)
{
	ECE bit = 1;			   //配置CK_CNT时钟来源为 TIM3_ETR引脚
	DIR bit = 0, CMS[1:0] = 0; //计数器为Upcounting mode
	OPM bit = 0;			   //计数器为循环模式
	TIM3_PSC = 0;		  	   //CK_CNT时钟不分频
	TIM3_ARR = 65535;
	UIE bit = 1;			   //使能Update interrupt
	
	ETPS[1:0] = 0;		  	   //TIM3_ETR输入信号不分频
	ETP bit = 0;			   //TIM3_ETR输入上升沿有效
	ETF[3:0] = 0;			   //TIM3_ETR输入信号不滤波
	
	配置TIM3_ETR引脚为上拉输入;
	在NVIC中设置timer3中断优先级并使能中断;

	CEN bit = 1;	//Start Counter
}            

3、测量TIM3_CH1引脚 输入的PWM信号的频率和占空比
  输入PWM的频率范围是 1Khz(period = 1MS,1%占空比时高电平周期为 10us) ~ 100Khz (period = 10us,1%占空比时高电平周期为 0.1us)

static uint8_t g_cc1_rising_edge_cnt;
static uint32_t g_pwm_frequency; 		// unit: 1hz
static uint8_t g_pwm_duty;				// unit: 1%

/* 函数功能:读 TIM3_CH1引脚输入的PWM频率和占空比. 此接口供外部模块调用 */
void timer3_pwm_frequency_duty_read(uint32_t *frequency, uint8_t *duty)
{
	if(frequency)
		*frequency = g_pwm_frequency;
	
	if(duty)
		*duty = g_pwm_duty;
}

static void timer3_measure_pwm_start(void)
{
	if(CEN bit == 1)
		return;
		
	CEN bit = 1;	//Start Counter
	CC1E bit = 1;	//使能CC1 capture
	CC2E bit = 1;	//使能CC2 capture
}

static void timer3_measure_pwm_stop(void)
{
	CEN bit = 0;	//Stop Counter
	CC1E bit = 0;	//禁能CC1 capture
	CC2E bit = 0;	//禁能CC2 capture
	g_cc1_rising_edge_cnt = 0;
}

static void timer3_irq_handler(void)
{
	if(CC1IF标志置位)	//TIM3 CC1 capture
	{
		清零CC1IF标志;
		(g_cc1_rising_edge_cnt)++;
		if(g_cc1_rising_edge_cnt >= 2)	//period = TIM3_CCR1, duty =  TIM3_CCR2 / TIM3_CCR1
		{
			if(TIM3_CCR1 == 0)	
		    {
		    	g_pwm_frequency = 0;
		    	duty = 0;
		    }
		    else
		    {
		    	g_pwm_frequency = 1000000 / (TIM3_CCR1 * 0.1);
		    	g_pwm_duty = (TIM3_CCR2 * 100) / TIM3_CCR1;
		    }
			
			timer3_measure_pwm_stop();	//CC1 capture中断不能一直使能, 否则若PWM频率太快会让CPU卡死在TIM3中断
		}
	}
	
	if(UIF标志置位)		//TIM3 Overflow
	{
		清零UIF标志;
		g_pwm_frequency = 0;
		if(TIM3_CH1引脚 为高电平)
		{
			g_pwm_duty = 100;
		}
		else
		{
			g_pwm_duty = 0;
		}
		
		timer3_measure_pwm_stop();
	}
}

static void timer3_init(void)
{
	ECE bit = 0, SMS[2:0] = 0b100; 	//配置CK_CNT时钟来源为内部时钟CK_INT, 且Slave mode为 Reset mode
	TS[2:0] = 0b101;				//TIMx_CH1信号的上升沿触发Counter reset
	DIR bit = 0, CMS[1:0] = 0;	 	//计数器为Upcounting mode
	OPM bit = 0;					//计数器为循环模式
	TIM3_PSC = 6;		  	  		//一个CK_CNT周期 ≈ 0.1us (CK_INT频率为 72Mhz)
	TIM3_ARR = 20000 - 1; 			//计数器 Overflow周期:2MS
	UIE bit = 1;			   		//使能Update interrupt
	
	CC1S[1:0] = 0b01;				//TIM3 CC1 channel为输入 且输入引脚为TIM3_CH1
	IC1PSC[1:0] = 0;				//TIM3 CC1 channel信号不分频
	IC1F[3:0] = 0;					//TIM3 CC1 channel信号不滤波
	CC1P bit = 0;					//CC1捕获上升沿
	CC1IE bit = 1;			     	//使能CC1 capture中断
	
	CC2S[1:0] = 0b10;				//TIM3 CC2 channel为输入 且输入引脚为TIM3_CH1
	IC2PSC[1:0] = 0;				//TIM3 CC2 channel信号不分频
	IC2F[3:0] = 0;					//TIM3 CC2 channel信号不滤波
	CC2P bit = 1;					//CC2捕获下降沿
	
	配置TIM3_CH1引脚为上拉输入;
	在NVIC中设置timer3中断优先级并使能中断;
	
	//TIM4定期启动TIM3测量PWM, 这样PWM的频率和占空比就可以自动更新.如果不是定期启动, 而是一直启动, 则CPU可能卡死在TIM3中断
	timer4_set(TIMOUT_1S, timer3_measure_pwm_start, ONE_SHOT_DISABLE);
	timer4_start();
}            

4、用TIM3_CH1引脚 输出指定频率和占空比的PWM波形

/* 函数功能:启动TIM3_CH1引脚输出PWM. 此接口供外部模块调用 */
void timer3_pwm_start(void)
{
	CC1E bit = 1;	//使能CC1 channel
	CEN bit = 1;	//Start Counter
}

/* 函数功能:停止TIM3_CH1引脚输出PWM. 此接口供外部模块调用 */
void timer3_pwm_stop(void)
{
	CC1E bit = 0;	//禁能CC1 channel
	CEN bit = 0;	//Stop Counter
}

/* 函数功能:设置 TIM3_CH1引脚输出的PWM, period:PWM周期, 单位 1ns, pulse:PWM高电平时间, 单位 1ns. 此接口供外部模块调用 */
void timer3_pwm_set(uint32_t period, uint32_t pulse)	
{
	uint64_t tim_clock, psc;
	
	tim_clock = 72;			//内部时钟CK_INT为 72Mhz
	period = (unsigned long long)period * tim_clock / 1000ULL;
    psc = period / 65535 + 1;
    TIM3_PSC = psc - 1;		//配置预分频TIM3_PSC
    
    period = period / psc;
    if(period < 2)
    {
    	period = 2;
    }
    TIM3_ARR = period - 1;	//配置TIM3_ARR
    
    pulse = (unsigned long long)pulse * tim_clock / psc / 1000ULL;
    TIM3_CCR1 = pulse;		//配置TIM3_CCR1
    
    TIM3_CNT = 0;
    UG bit = 1;				//软件触发Update event, 以便让TIM3更新各寄存器
}

static void timer3_init(void)
{
	ECE bit = 0, SMS[2:0] = 0; 	//配置CK_CNT时钟来源为内部时钟CK_INT
	DIR bit = 0, CMS[1:0] = 0;	//计数器为Upcounting mode
	OPM bit = 0;				//计数器为循环模式
	TIM3_PSC = 71;		  	  	//一个CK_CNT周期 = 1us (CK_INT频率为 72Mhz)
	TIM3_ARR = 1000 - 1; 		//计数器 Overflow周期:1MS
	ARPE bit = 1;			    //使能寄存器TIM3_ARR的preload功能
	
	CC1S[1:0] = 0;				//TIM3 CC1 channel为输出
	CC1P bit = 0;				//TIMx_CH1引脚高电平有效
	OC1M[2:0] = 0b110;			//TIM3 CC1 channel为 PWM mode1
	OC1PE bit = 1;				//使能寄存器TIM3_CCR1的preload功能
	
	配置TIM3_CH1引脚为复用推挽输出;
}            

三、注意事项

1、UDIS bit 使用场景

  软件写 preload register时,先置位UDIS bit ,然后写preload register,最后再清零UDIS bit,这样可以避免以下两个动作产生冲突:
  updating shadow register from preload register
  writing preload register

2、URS bit 使用场景

  使用CC1和 CC2通道测量输入PWM波形的频率和占空比时,CC1通道采样PWM上升沿,CC2通道采样PWM下降沿,Slave mode为Reset mode 并选择 CC1通道的信号为 TRGI,当CC1通道发生捕获时,TRGI信号会置位UG标志,此时如果UIE标志为1,则会触发 Update interrupt,换句话说,每次CC1通道发生捕获,都会触发Update interrupt,结果就是CPU会频繁响应Update interrupt。

  在这种场景下,软件可以配置 URS bit为0,这样当UIE标志为1时,Counter overflow/underflow时会触发 Update interrupt,但CC1通道发生捕获时,不会触发 Update interrupt。

3、容易混淆的概念:
① 触发Counter reset 条件:软件置位UG标志,或Slave mode为Reset mode时(SMS bits = 100),TRGI信号置位UG标志。
  Upcounting mode或 Center-aligned mode:reset时,Counter值更新为 0
  Downcounting mode:reset时,Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)

② 计数器溢出时,更新Counter值的四种情况:

  Upcounting mode & Counter overflow:Counter值更新为 0

  Downcounting mode & Counter underflow:Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)

  Center-aligned mode & Counter overflow:Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)

  Center-aligned mode & Counter underflow:Counter值更新为 0

③ 触发Update event条件:UDIS标志为0时,(Counter overflow/underflow) || (软件或 TRGI信号 置位UG标志)。
  PS:Update event触发时,preload register的值自动更新到 shadow register

④ 触发UIF标志置位条件:
​  Counter overflow/underflow
  or URS标志为0时,软件或 TRIG信号 置位 UG标志

  PS:UIE标志、UDE标志为1时,UIF标志置位会触发 Update interrupt和 Update DMA request

4、用OCx输出PWM时

  PWM1 mode + Downcounting mode无法输出 0%占空比,

  PWM2 mode + Downcounting mode无法输出 100%占空比。

5、用OCx输出PWM时,如果想在停止输出PWM时控制 OCx电平,可将CC1 channel的输出模式 从PWM mode 改为 force mode。

6、使用TIMx输出PWM时,建议使能TIMx_CCRx 和 TIMx_ARR的preload功能,否则当修改寄存器时,会影响上一个输出的PWM波形。

7、关于高级定时器TIM1&TIM8:

  有Repetition Counter Register:配合计数器的One-pulse mode,可控制在CCx channel上输出的PWM的周期数量,比如控制CC1 channel输出 5个周期的波形。

  一个OCxREF信号可同时控制 OCx、OCxN互补输出:如果只使用OCx、OCxN当中一个引脚输出PWM,注意OCx、OCxN不能同时使能,并且需使能CCx的Main output。

在这里插入图片描述

参考资料

[1] STM32F103xx datasheet.

[2] STM32F10xxx reference manual.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值