10.定时器各功能分析及编码

2023-12-30 16:56:18

知识汇总:

STM32的定时器有三种,高级定时器,通用定时器,基本定时器

就是功能多与少的差别,下面来逐个解释功能:在此之前,需要对几个概念有认知

几个概念:

1.定时器时钟频率:

????????和定时器每计数一次所花时间有关,其倒数就是时钟周期

2.计数值:

????????例如是自增的,就是每隔一个时钟周期就会自加1,就是所谓的计数了

3.重载值:

????????就是计数值计数到多少就会溢出的那个值,一般计数值自加自加,直到等于重载值时,计数器又会从零开始自加。例如设置重载值是5000,打开定时器,计数器(对应计数值)就开始自增,0,1,2,3.。。。。直到等于5000,然后计数器又从0开始自增,0,1,2,。。。5000

4.重复值:

????????重复值就是计数器自加到重载值这个重复过程的次数(溢出次数),这个比较少用可以不管。

5.比较值:

????????这个值一般比重载值要小,简单地说就是定时器有一个相关的IO口,当计数值小于比较值的时候,这个IO口就输出高电平,因为计数值一直自增嘛,总有一天会大于比较值的,当计数值大于比较值的时候,这个IO口就输出低电平,比较值主要就是这个作用,可以想象这个比较值是控制一个输出波形占空比的关键。

6.stm32f40x下定时器时钟频率:

这个可把我坑惨了,在写输入捕获的时候发现怎么捕获时间误差那么大,后来排查了好一阵,发现好家伙,原来APBx下的定时器时钟频率是该APBx时钟频率的两倍。

所以附属于APB1的定时器的时钟频率是84M,附属于APB2的定时器的时钟频率是168M频率(这一次编写代码的TIM1就是APB2下的时钟)

至于原理,没什么道理就是手册讲了的,如果APB的分频不是1,那么定时器的频率就是APB的两倍:

(还是得认真看手册!)

下面正式进入

功能解释:

定时中断:

????????很显然,就是配置好时钟频率、重载值,打开中断,打开定时器,然后每隔有一段固定的时间就会进入一次中断(计数值等于重载值时产生中断,叫作溢出中断)。例如时钟频率是1M,那每计数一次就是1/1M = 1us,如果重载值设为5,那就是计数5次溢出一次即中断一次嘛,所以间隔的时间就是? ? 时钟周期*重载值,1us*5 = 5us。

输入捕获模式:

????????这个也很好理解,就是用来计算输入的波形高电平或低电平持续时间,设置成捕获脉冲的高电平持续时间,那当定时器对应的IO引脚,检测到上升沿时,就会将计数值清零并开始计数,当检测到下降沿时,就会触发中断,这时我们在中断里就可以取出计数值,从而知道高电平持续了多长时间。捕获脉冲低电平也是一样的原理。简言之输入捕获就是可以获取外部输入的脉冲高电平或低电平持续时间的。

PWM输入模式:

????????这个其实是输入捕获模式的一个特例,输入捕获模式可以捕获高电平或低电平持续时间,而PWM输入模式可以检测出定时器相关IO引脚输入的PWM波形的占空比以及PWM周期。如下图,关注红框里的即可,它就是在上升沿的时候,计数器开始计数,然后下降沿的时候标记极性要跳转了,接着在下一个上升沿的时候触发中断。两个上升沿之间的时间就是PWM总的周期时间,而根据电平跳变前后的时间,就能算出占空比是多少了~

编码器模式:

? ? ? ? 这个就给力了,驱动编码电机时很好用,可以读出电机转过角度对应编码器产生的脉冲数,因为电机转一圈,对应编码器产生的脉冲数是固定的嘛,所以你知道了脉冲数,就可以知道电机转了多少圈,而且如果你固定间隔地去读脉冲数,还可以知道电机的速度(路程/时间 = 速度)。

????????其实原理也很简单,就是捕获到一个上升沿,计数值就加一,例如一个编码电机转一圈其编码器会产生330个脉冲,那就是有330个上升沿嘛,这样我们通过记录捕获到的上升沿的个数,就能知道电机转了多少圈了。一个编码器一般会用到定时器的两个IO口,一个是用于编码器A相一个是B相,简单理解就是,如果是电机正转,A会比B多产生一个脉冲,如果是电机反转,则A会比B少产生一个脉冲,这样两个定时器IO相互配合,我们就能确切地知道,这个编码电机是转了多少圈,而且是往哪个方向转的。那又会产生一个疑惑,定时器只有一个计数器,那计数器是计数A相IO的脉冲还是B相IO的脉冲呢?其实这是可选的,可以只记录A的也可以只记录B的,也可以两个都记录,那电机转一圈,记录到的脉冲数无非就是两倍而已。

以上说的都是定时器的输入功能,下面讲输出的功能

死区和互补输出:

高级定时器的输出可带死区和互补输出,死区的意思就是比如你输出一个PWM波,死区可以使得定时器IO实际输出的PWM波上升沿或下降沿比原来预设的要延迟一段时间再产生,互补输出就是定时器两个IO嘛,一个输出高的时候另一个就是低电平,例如下图实际输出波形1和2:

为什么设计死区呢?因为单片机的IO高电平和切换的速度很快,而电机高电平和低电平切换速度很慢(或者是电机驱动器),这就导致单片机IO输出高电平的时候,电机的IO还在低电平,这样就属于预设之外的操作了。

强制输出模式:

????????最开始的时候我们有提到,定时器输出就是定时器的一个IO口,在计数值小于比较值时,IO输出高电平,计数器大于比较值时,IO输出低电平(电平关系可以互换,这个称为极性)。强制输出模式就是不管计数器和比较值大于还是小于,我设这个IO高电平它就高电平,低电平就低电平。

输出比较模式:

????????这个就很好理解了,如上面所说计数值小于比较值时,IO输出高电平,计数器大于比较值时,IO输出低电平(极性可设置)

PWM模式:

????????这个很常用,设置好时钟频率(计数一次多少时间),重装值(决定PWM周期),比较值(配合极性,决定占空比),最后使能自动重载,这样定时器相关的IO就会源源不断地输出自定义频率、占空比的PWM波了。其实也是输出比较模式的一个特例而已,不能说很像,只能说一模一样。

单脉冲模式:

????????这个很特别,集输入与输出一体,就是定时器一个IO捕获到上升沿或者下降沿(称为触发),另一个IO就可以输出一个指定延时期限且指定宽度的脉冲,一次触发只输出一个脉冲噢。

值得一提的是,一个具有输入输出功能的定时器会有4个通道,每个通道其实对应一个IO口,一个定时器四个通道的计数值,重载值是共用的,而比较值还有输入输出等则由通道自己决定。至于基础定时器,没有输出或者输入功能,自然是没有IO口的。

代码编写:

定时器核心四要素1.时钟频率 2.重载值&&计数值&&比较值?3.中断事件 4.定时器配置和开关

先概览一下都有哪些寄存器:
控制寄存器1(TIMx_CR1:通用配置 )

控制寄存器2(TIMx_CR2:通道相关的 )

从模式控制寄存器(TIMx_SMCR)

DMA/中断使能寄存器(TIMx_DIER)

状态寄存器(TIMx_SR,中断标志)

事件生成寄存器(TIMx_EGR)

捕获/比较模式寄存器1(TIMx_CCMR1)

捕获/比较模式寄存器2(TIMx_CCMR2)

捕获/比较使能寄存器1(TIMx_CCER)

计数器(TIMx_CNT)

预分频器(TIMx_PSC)

自动重载寄存器 (TIMx_ARR)

重复计数器寄存器 (TIMx_RCR)

捕获/比较寄存器 1~4 (TIMx_CCR1~4,一个通道一个)

断路和死区寄存器 (TIMx_BDTR)

DMA 控制寄存器 (TIMx_DCR)

全传输 DMA 地址寄存器 (TIMx_DMAR)

因为高级定时器功能最多,就以高级定时器1来编写了~其它定时器也是一样的。

1.定时中断功能代码编写:

目标:

假如我们的目标是让定时器每500ms中断一次,而且计数频率设为10K---->>如果APB2时钟是84M,那么定时器时钟就是168M,时钟预分频就是84M*2 / 10k ?= 8400*2 ,那么计数次数就是 0.5秒除以计数周期 = 0.5*10K = 5000次。

即预分频值设为8400*2 -1,重载值设为5000-1。

以上应该很好理解,单片机最常见的时间计算。

高级定时器一共有20个寄存器呢,我们仍然按照功能来找寄存器,够用就好,这样编码不会太复杂

1.时钟频率:

TIM1是依附于APB2的,根据时钟系统我们知到APB2是168M/2 = 84M,而且要时钟使能

寄存器TIM1->PSC是时钟预分频设为8400*2-1

RCC->APB2ENR|=1<<0;	//TIM1时钟使能 
TIM1->PSC =?8400*2-1;

2.重载值&&计数值&&比较值:

重载值设为5000-1

TIM1->ARR = 5000-1;

在这里不用写比较值~比较值是和输入/输出功能有关的

?3.中断事件

TIM1->DIER:

bit[0]? ? ? ? 更新中断使能 1<<0

TIM1->SR:

?bit[0]? ? ? ? 更新中断标志,要手动清零

TIMx_DIER |= 1<<0;//打开更新中断
MY_NVIC_Init(1,3,TIM1_UP_TIM10_IRQn,2);

中断服务函数:
void TIM1_UP_TIM10_IRQHandler(void)
{
	if(TIM1->SR&0X0001)//溢出中断
	{
			    				   				     	    	
	}				   
	TIM1->SR&=~(1<<0);//清除中断标志位 	    
}

4.定时器配置和开关

相关寄存器是TIM1->CR1:

bit[7]? ? ? ? ? ?ARPE控制自动重载的 1<<7,这个位很关键,1的话,重载值和影子寄存器的值同步,0的话,在每次发生更新事件时才将重载值同步到影子寄存器。可以这样理解,影子寄存器是正真生效的重载值。例如你在输出pwm波时,如果中途改了参数,这个位如果是0,那么PWM波会在下一个周期再变成新参数的波形,如果是1的话,那就是当前周期就变成新参数的波形了,只是准不准就不好说了。

bit[6:5]? ? ? ? 对齐模式选择,选边沿对齐模式,就是计数器是从0到重载值或从重载值到0这样计数 ???????????????????0x0<<5

bit[4]? ? ? ? ? ?递增计数 0<<4

bit[3]? ? ? ? ? ?不使用单脉冲模式 0<<3

bit[2]? ? ? ? ? ?URS选择溢出、UG等事件都能触发中断/DMA 0<<2。更新(UG)事件就是重复值达到重复值寄存器设置的值时触发的事件。UG事件可以软件主动产生。更新中断会让定时器从0开始计数并且更新 重复值、重载值、时钟预分频值,溢出事件和更新事件都会触发更新中断。

bit[1]? ? ? ? ? ?UDIS允许溢出和UG事件 0<<1,写1的话则是只保留溢出中断,而禁止UG事件,禁止UG事件的好处是,产生的每个波形都是完整的,就算有参数改变也会在下一个周期才生效。

bit[0]? ? ? ? ? ?计数器使能 1<<0,这个等最后再打开

配置:
TIM1->CR1 =?0<<7 |?0x0<<5 |?0<<4 |?0<<3 |?0<<2 |?0<<1 | 0<<0 ;//傻眼,全0
打开定时器1:
TIM1->CR1 |= 1<<0;

5.完整代码://实测可用


void TIM1_Int_Init(void)
{
//1.时钟频率
RCC->APB2ENR|=1<<0;	//TIM1时钟使能 
TIM1->PSC =?8400*2-1;

//2.重载值&&计数值&&比较值
TIM1->ARR = 5000-1;

//3.中断事件 
TIM1->DIER |= 1<<0;//打开更新中断
MY_NVIC_Init(1,3,TIM1_UP_TIM10_IRQn,2);


//4.定时器配置和开关
TIM1->CR1 =?0<<7 |?0x0<<5 |?0<<4 |?0<<3 |?0<<2 |?0<<1 | 0<<0 ;//傻眼,全0
TIM1->CR1 |= 1<<0;//打开定时器1

}


//中断服务函数:
void TIM1_UP_TIM10_IRQHandler(void)
{
	if(TIM1->SR&0X0001)//溢出中断
	{
	    printf("TIM1_UP_TIM10_IRQHandler\r\n"); 		    				   				     	    	
	}				   
	TIM1->SR&=~(1<<0);//清除中断标志位 	    
}

比较通用的初始化:

//APB2 是84M
void TIM1_Int_Init(u16 arr重载值,u16 psc预分频值)
{
	RCC->APB2ENR|=1<<0;	//TIM1时钟使能    
 	TIM1->ARR=arr;  	//设定计数器自动重装值 
	TIM1->PSC=psc;  	//预分频器	  
	TIM1->DIER|=1<<0;   //允许更新中断	  
	TIM1->CR1|=0x01;    //使能定时器1
  	MY_NVIC_Init(1,3,TIM1_UP_TIM10_IRQn,2);	//抢占1,子优先级3,组2									 
}

运行效果:500ms中断一次

2.输入捕获模式代码编写:

????????上面定时器中断还比较简单,找到几个关键的点就能写代码,但是输入捕获模式,浏览一下寄存器明显涉及的比较多,这样就需要我们将寄存器分类,然后再根据手册逐一去看哪些和功能相关,上面罗列了一堆的寄存器,现在按照定时器核心4部曲将它们分类:

1.时钟频率 
预分频器(TIMx_PSC) 

2.重载值&&计数值&&比较值 
自动重载寄存器 (TIMx_ARR),
计数器(TIMx_CNT),
捕获/比较模式寄存器1(TIMx_CCMR1) 
捕获/比较模式寄存器2(TIMx_CCMR2) 
捕获/比较使能寄存器1(TIMx_CCER) 
捕获/比较寄存器 1~4 (TIMx_CCR1~4,一个通道一个), 

3.中断事件 
DMA/中断使能寄存器(TIMx_DIER) 
状态寄存器(TIMx_SR,中断标志) 

4.定时器配置和开关 
控制寄存器1(TIMx_CR1:通用配置 ) 
控制寄存器2(TIMx_CR2:通道相关的 )

然后查看手册,看看有没有相关的初始化或者使用的示例,一般都会有的,比较复杂,步骤比较多的都会有。且看,参考手册就有这样一个捕获的例子,后面我们会结合定时器核心思想和示例来找寄存器并配置。

下面逐一编写代码:

目标:

以100000hz(5个零)的时钟频率,去捕获TIM1通道1对应的IO的高电平持续时间(IO会事先拉低,然后用杜邦线接高电平一段时间),用 串口打印出来。

0.GPIO相关的

因为这个模式涉及到输入输出,即与GPIO有关,所以GPIO需要设置一下,还是之前提到过的GPIO复用思路:1.时钟 2.IO 3.复用啥

通过查询芯片数据手册,可以知道PE9的复用功能是TIM1_CH1

所以代码编写如下:

1.时钟
RCC->AHB1ENR|=1<<4;   	//GPIOE时钟附属于AHB1
2.IO  
GPIO_Set(GPIOE,PIN9,GPIO_MODE_AF,0,0,GPIO_PUPD_PD);//PA9,PA10,都配置为复用模式,电气配置为下拉
3.复用啥
GPIO_AF_Set(GPIOE,9,1);	//PE9,AF1 是定时器1
	   

1.时钟频率?

?预分频器(TIMx_PSC)?

?TIM1是依附于APB2的,根据时钟系统我们知道APB2是168M/2 = 84M,则定时器频率是168M,而且要时钟使能

要让时钟频率是100000,所以预分频值应设为1680,因为168000000 / 100000 = 1680,即

这样一来,记一次数就是1/100000 = 10^-5 秒

RCC->APB2ENR|=1<<0;	//TIM1时钟使能 
TIM1->PSC =?1680-1;

2.重载值&&计数值&&比较值?

自动重载寄存器 (TIMx_ARR),

重载值自然是越大越好,这样可以采集高电平的时间,一次才更长,TIM1是16位的定时器,所以最大可以设置为0xFFFF,这样溢出一次就是65535*0.00001 = 0.65535秒

TIM1->ARR =?0xFFFF;


计数器(TIMx_CNT),这个不用设置,或者初始化为0

TIM1->CNT = 0;

这里寄存器比较多,但是我们心中有数,因为手册有提到怎么配置:
捕获/比较模式寄存器1(TIMx_CCMR1)?

bit[1:0]? ? ? ? 通道1设为输入,且IC1映射到TI1 0x1<<0

bit[3:2]? ? ? ? 每检测一个边沿就执行捕获?0x0<<2

bit[7:4]? ? ? ? 不配置滤波器0x0<<4

8~15位是通道2的,和我们通道1没啥关系,不管它

TIM1->CCMR1 &=0xFF00;

TIM1->CCMR1 |=0X1<<0;


捕获/比较模式寄存器2(TIMx_CCMR2)?

这个寄存器和上面的类似,只是是通道3和通道4的,不管


捕获/比较使能寄存器1(TIMx_CCER)?

bit[0]? ? ? ? 1<<0 使能捕获

bit[1]? ? ? ? 0<<1 上升沿捕获? 1<<1 下降沿捕获

bit[2]?和 bit[3] 是关于互补输出的,不管。每一个通道有4个位,刚好这个寄存器16个位,满足4个通道的配置

TIM1->CCER |= 0<<1 | 1<< 0 ;


捕获/比较寄存器 1~4 (TIMx_CCR1~4,一个通道一个),?

TIM1->CCR1就是保存着通道1捕获的计数值

3.中断事件?

DMA/中断使能寄存器(TIMx_DIER)?

我们既要开启输入捕获中断,也要开启溢出中断~

bit[0]? ? ? ? 更新中断使能 1<<0;

bit[1]? ? ? ? CC1中断时钟 1<<1;

状态寄存器(TIMx_SR,中断标志)?

TIM1->DIER |= 1<<1 | 1<<0;
MY_NVIC_Init(2,0,TIM1_CC_IRQn,2);
MY_NVIC_Init(2,1,TIM1_UP_TIM10_IRQn,2);

//中断服务函数,有两个
//捕获状态
//[7]    0,没有成功的捕获;1,成功捕获到一次.
//[6]    0,还没捕获到低电平;1,已经捕获到低电平了.
//[5:0]:捕获低电平后溢出的次数
//(我们前面说溢出一次是0.65535秒嘛,这里的5个位就是记录溢出了多少次的,最大是0x3f即63)
//也就是说,最多最多可以捕获63*0.65535 = 41.28705秒的脉宽
u8  TIM1CH1_CAPTURE_STA=0;	//输入捕获状态	这个记录最后一次捕获但还没溢出的那部分时间
	    				
u16	TIM1CH1_CAPTURE_VAL;	//输入捕获值(TIM1是16位)
//捕获中断
void TIM1_CC_IRQHandler(void)
{ 
    
	u16 tsr;
    //printf("TIM1_CC_IRQHandler\r\n");
	tsr=TIM1->SR;
 	if((TIM1CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{

		if(tsr&0x02)//捕获1发生捕获事件
		{	
			if(TIM1CH1_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
			{
				TIM1CH1_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
			    TIM1CH1_CAPTURE_VAL=TIM1->CCR1;	//获取当前的捕获值.
	 			TIM1->CCER&=~(1<<1);			//CC1P=0 设置为上升沿捕获
                 printf("get doooooooooooon\r\n");
			}else  								//还未开始,第一次捕获上升沿
			{
				TIM1CH1_CAPTURE_STA=0;			//清空
				TIM1CH1_CAPTURE_VAL=0;
				TIM1CH1_CAPTURE_STA|=0X40;		//标记捕获到了上升沿
				TIM1->CR1&=~(1<<0)		;    	//关掉定时器
	 			TIM1->CNT=0;					//计数器清空
	 			TIM1->CCER|=1<<1; 				//CC1P=1 设置为下降沿捕获
				TIM1->CR1|=0x01;    			//使能定时器
                printf("get uppppppppppp\r\n");
			}
		}			     	    					   
 	}
	 TIM1->SR&=~(1<<1);//清除溢出中断标志位    
}
//溢出中断
void TIM1_UP_TIM10_IRQHandler(void)
{ 
    u16 tsr;
    tsr=TIM1->SR;
    //printf("TIM1_UP_TIM10_IRQHandler\r\n");
 	if((TIM1CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{
    	if(tsr&0X01)//溢出
		{	  
            printf("TIM1_UP_TIM10_IRQHandler\r\n");            
			if(TIM1CH1_CAPTURE_STA&0X40)//查看bit6,已经捕获到高电平了
			{
				if((TIM1CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM1CH1_CAPTURE_STA|=0X80;		//标记成功捕获了一次
					TIM1CH1_CAPTURE_VAL=0XFFFF;
				}else TIM1CH1_CAPTURE_STA++;
			}	 
		}
    }
    TIM1->SR&=~(1<<0);//清除溢出中断标志位   
}

4.定时器配置和开关?

控制寄存器1(TIMx_CR1:通用配置 )?

和上面的一样即可

bit[7]? ? ? ? ? ?ARPE控制自动重载的 0<<7,上面有解释

bit[6:5]? ? ? ? 对齐模式选择,选边沿对齐模式 0x0<<5

bit[4]? ? ? ? ? ?递增计数 0<<4

bit[3]? ? ? ? ? ?不使用单脉冲模式 0<<3

bit[2]? ? ? ? ? ?选择溢出、UG等事件都能触发中断/DMA 0<<2。

bit[1]? ? ? ? ? ?允许溢出和UG事件 0<<1

bit[0]? ? ? ? ? ?计数器使能 1<<0,这个等最后再打开

配置:
TIM1->CR1 =?0<<7 |?0x0<<5 |?0<<4 |?0<<3 |?0<<2 |?0<<1 | 0<<0 ;//傻眼,全0
打开定时器1:
TIM1->CR1 |= 1<<0;


控制寄存器2(TIMx_CR2:通道相关的 )

这个不用管~默认值即可

完整代码:

初始化的:

void TIM1_CH1_Cap_Init(void)
{
//GPIO相关的
//1.时钟
RCC->AHB1ENR|=1<<4;   	//GPIOE时钟附属于AHB1
//2.IO  
GPIO_Set(GPIOE,PIN9,GPIO_MODE_AF,0,0,GPIO_PUPD_PD);//PA9,PA10,都配置为复用模式,电气配置为下拉
//3.复用啥
GPIO_AF_Set(GPIOE,9,1);	//PE9,AF1 是定时器1

//定时器相关的
//1.时钟频率
RCC->APB2ENR|=1<<0;	//TIM1时钟使能 
TIM1->PSC =?84-1;//时钟频率1Mhz

//2.重载值&&计数值&&比较值?
TIM1->ARR =?0xFFFF;//重载值
TIM1->CNT = 0;
TIM1->CCMR1 &=0xFF00;
TIM1->CCMR1 |=0X1<<0;//配置通道1为输入,一边沿一捕获,不滤波
TIM1->CCER |= 0<<1 | 1<< 0 ;//上升沿,使能捕获

//3.中断事件?
TIM1->DIER |= 1<<1 | 1<<0;//使能更新及捕获中断
MY_NVIC_Init(2,0,TIM1_CC_IRQn,2);
MY_NVIC_Init(2,1,TIM1_UP_TIM10_IRQn,2);
TIM5->EGR=1<<0;//手动产生更新中断,目的是立即刷新预分频、重载值等参数

//4.定时器配置和开关?
TIM1->CR1 =?0<<7 |?0x0<<5 |?0<<4 |?0<<3 |?0<<2 |?0<<1 | 0<<0 ;//傻眼,全0
TIM1->CR1 |= 1<<0;//打开定时器1:

}

中断的:

看上面3.中断事件

运行效果:

因为PE9上面是设置为下拉的嘛,默认是低电平,那我把它连到我自己做的稳压板的3.3V上,如果打开稳压板总电源,就上电,PE9就会从低电平变成高电平(第一次捕获),此时PE9捕获到上升沿,开始计时,然后我关掉稳压板总电源,PE9就会从高电平变为低电平(第二次捕获),就停止捕获,并把高电平时间打印出来。

由于我两只手不一致有一丢丢误差,但是很明显是ok的~?

3.PWM输入模式代码编写:

目标:

以10000hz(4个零)的时钟频率,去捕获TIM1通道1对应的IO输入的波形,并打印出每个PWM波,高电平持续时间,低电平持续时间,占空比,PWM波周期

同样手册说了使用流程,还是按我们的思路,把寄存器套进去。这个和上面输入捕获差不太多,应该只需要改一改就行了。

把上面的配置阔皮下来修改:

void TIM1_CH1_PWMCap_Init(void)
{
//GPIO相关的
//1.时钟
RCC->AHB1ENR|=1<<4;   	//GPIOE时钟附属于AHB1
//2.IO  
GPIO_Set(GPIOE,PIN9,GPIO_MODE_AF,0,0,GPIO_PUPD_PD);//PA9,PA10,都配置为复用模式,电气配置为下拉
//3.复用啥
GPIO_AF_Set(GPIOE,9,1);	//PE9,AF1 是定时器1

//定时器相关的
//1.时钟频率
RCC->APB2ENR|=1<<0;	//TIM1时钟使能 
TIM1->PSC =16800-1;//时钟频率10000HZ,计数一次0.0001s,即100us一次

//2.重载值&&计数值&&比较值?
TIM1->ARR =0xFFFF-1;//重载值 那么溢出一次最长是65535*100us = 6.5535s
TIM1->CNT = 0;
TIM1->CCMR1 &=0x0000;
//下面几句句是核心,简言之就是把通道1对应的IO,用通道1和通道2两个比较器来处理,比较器1负责捕获上升沿,比较器2负责捕获下降沿,这样两个一配合就能一次性得到PWM的核心参数--PWM周期、高电平持续时间
TIM1->CCMR1 |=0x2<<8 | 0X1<<0;//配置通道1为输入,一边沿一捕获,不滤波;通道2映射到输入1

TIM1->CCER |= 1<<5 | 1<<4 | 0<<1 | 1<< 0 ;//通道1上升沿,使能捕获,通道2下降沿,使能捕获

//下面两句是按照手册要求写的,简言之就是检测到上升沿就触发中断以后,执行定时器复位信号,计数值、比较值都清空,这样就可以检测下一个PWM波了
TIM1->SMCR &=~(0X7<<4 | 0x7<<0);
TIM1->SMCR |= 0X5<<4 | 0x4<<0;//触发选择:滤波后的定时器输入 1 (TI1FP1),复位模式

//3.中断事件 没怎么改,就加上把通道2的比较器捕获中断打开了而已
TIM1->EGR=1<<0;//手动产生更新中断,目的是立即刷新预分频、重载值等参数
TIM1->DIER |= 1<<2 |1<<1 | 1<<0;//使能更新及捕获中断通道1和通道2
MY_NVIC_Init(2,0,TIM1_CC_IRQn,2);
MY_NVIC_Init(2,1,TIM1_UP_TIM10_IRQn,2);


//4.定时器配置和开关?
TIM1->CR1 =0<<7 | 0x0<<5 |0<<4 |0<<3 |  0<<2 | 0<<1 | 0<<0 ;//傻眼,全0
TIM1->CR1 |= 1<<0;//打开定时器1:

}

????????配置完以后,定时器的功能就和手册给的框图一模一样了,下降沿时,记录了高电平持续时间(存在比较器CCR2中),而上升沿时,记录了整个PWM波周期(存在比较器CCR1中),这样我们只需要在捕获1(CCR1)中断发生时,去读出CCR1和CCR2的值,结合时钟频率,我们就能知道高电平是多长时间,PWM周期是多长,占空比是多少了。

中断服务函数:

//捕获中断
void TIM1_CC_IRQHandler(void)
{ 
    
	u16 tsr;
	tsr=TIM1->SR;
   

 	if((TIM1CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{

		if(tsr&0x02)//通道1发生捕获事件,PWM周期 上升沿
		{


				TIM1CH1_CAPTURE_STA|=0X80;		//标记成功捕获到一次高电平脉宽
			    TIM1CH1_CAPTURE_VAL=TIM1->CCR1;	//PWM周期.
                TIM1CH2_CAPTURE_VAL=TIM1->CCR2;//高电平时间
                printf("have get pwmmmmmmmmmm\r\n");

                TIM1->SR&=~(1<<2 | 1<<1 );//清除捕获通道1中断标志位
		}

 	}
	 
}
//溢出中断
void TIM1_UP_TIM10_IRQHandler(void)
{ 
    u16 tsr;
    tsr=TIM1->SR;
    printf("\r\n\r\nTIM1_UP_TIM10_IRQHandler\r\n\r\n");
 	if((TIM1CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{
    	if(tsr&0X01)//溢出
		{  
            //printf("TIM1_UP_TIM10_IRQHandler\r\n");            

		}
    }
    TIM1->SR&=~(1<<0);//清除溢出中断标志位   
}

main函数:

    TIM1_CH1_Cap_Init();

   	while(1)
	{ 		
        if(TIM1CH1_CAPTURE_STA&0X80)//成功捕获到了一次高电平
		{
			printf("PWM:%d ms \t PWM-HIGH:%d ms \t Q:%f \r\n",TIM1CH1_CAPTURE_VAL/10,TIM1CH2_CAPTURE_VAL/10,((TIM1CH2_CAPTURE_VAL)*1.0)/TIM1CH1_CAPTURE_VAL);//
			TIM1CH1_CAPTURE_STA=0;			//开启下一次捕获
		}
    }

运行效果:

还是一样,用按键开关来模拟波形~有信号发生器的可以用信号发生器。

4.编码器模式代码编写:

目标:

用TIM1的通道1和通道2,以100000hz(5个零)的时钟频率,读取编码电机转一圈的脉冲数。

(2023/12/30 标记了一处未完成,要期末了,在复习,且先发写了一半的,剩下的考完试再写,毕竟这可是关系到奖学金我的小钱钱捏)

文章来源:https://blog.csdn.net/weixin_62622990/article/details/135164759
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。