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

news2025/4/24 22:10:11

知识汇总:

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 标记了一处未完成,要期末了,在复习,且先发写了一半的,剩下的考完试再写,毕竟这可是关系到奖学金我的小钱钱捏)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1345485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

简单vlan划分和dhcp中继(Cisco Packet Tracer模拟)

文章目录 1. 前言2. 功能实现2.1. dhcp服务器接入2.2. 学校web服务器2.3. 设置学校dns服务器2.4. 设置线路冗余2.5. 配置ac。 1. 前言 在这里我们的计网作业是使用思科的Cisco Packet Tracer进行对校园网的简单规划&#xff0c;这里我对校园网进行了简单的规划&#xff0c;功能…

python+django在线学习教学辅助作业系统gp6yp

本课题使用Python语言进行开发。基于web,代码层面的操作主要在PyCharm中进行&#xff0c;将系统所使用到的表以及数据存储到MySQL数据库中 技术栈 后端&#xff1a;pythondjango 前端&#xff1a;vue.jselementui 框架&#xff1a;django/flask Python版本&#xff1a;python3.…

ES6语法(五)封装模块化公共工具函数、引入npm包 ,并上传到npm中进行下载

1. 模块化 模块化是指将一个大的程序文件&#xff0c;拆分为许多小的文件&#xff08;模块&#xff09;&#xff0c;然后将小的文件组合起来。 1.1. 优点 &#xff08;1&#xff09;防止命名冲突 &#xff08;2&#xff09;代码复用 &#xff08;3&#xff09;高维护性 &…

RHCE9学习指南 第13章 硬盘管理

新的硬盘首先需要对硬盘进行分区和格式化&#xff0c;首先了解一下硬盘的结构&#xff0c;如图13-1所示。 图13-1 磁盘上的磁道和扇区 硬盘的磁盘上有一个个的圈&#xff0c;每两个圈组成一个磁道。从中间往外发射线&#xff0c;把每个磁道分成一个个的扇区&#xff0c;每个扇…

助力城市部件[标石/电杆/光交箱/人井]精细化管理,基于YOLOv8全系列模型【n/s/m/l/x】开发构建生活场景下城市部件检测识别系统

井盖、电杆、光交箱、通信箱、标石等为城市中常见部件&#xff0c;在方便居民生活的同时&#xff0c;因为后期维护的不及时往往会出现一些“井盖吃人”、“线杆、电杆、线缆伤人”事件。造成这类问题的原因是客观的多方面的&#xff0c;这也是城市化进程不断发展进步的过程中难…

Springboot 不重启热重载静态资源文件

看了很多中文博客,都liveRload插件,或者其他什么什么......,一点用都没 解决办法:

听GPT 讲Rust源代码--src/tools(38)

File: rust/src/tools/clippy/clippy_dev/src/lib.rs rust/src/tools/clippy/clippy_dev/src/lib.rs文件是Clippy开发工具的入口文件&#xff0c;其作用是提供Clippy开发过程中所需的功能和工具。Clippy是一个Rust代码的静态分析工具&#xff0c;用于提供各种有用的代码规范、编…

什么是检索增强生成?

检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;是指对大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;输出进行优化&#xff0c;使其能够在生成响应之前引用训练数据来源之外的权威知识库。LLM 用海量数据进行…

KG+LLM(一)KnowGPT: Black-Box Knowledge Injection for Large Language Models

论文链接&#xff1a;2023.12-https://arxiv.org/pdf/2312.06185.pdf 1.Background & Motivation 目前生成式的语言模型&#xff0c;如ChatGPT等在通用领域获得了巨大的成功&#xff0c;但在专业领域&#xff0c;由于缺乏相关事实性知识&#xff0c;LLM往往会产生不准确的…

STM32F407ZGT6定时器(学习笔记一)

定时器STM32非常重要的外设&#xff0c;也是比较复杂的外设&#xff0c;下面以STM32F407ZGT6为例记录学习内容&#xff1a;&#xff08;1&#xff09;基本定时功能&#xff0c;&#xff08;2&#xff09;PWM输出功能&#xff0c;&#xff08;3&#xff09;PWM互补死区、多通道移…

分类模型评估方法

1.数据集划分 1.1 为什么要划分数据集? 思考&#xff1a;我们有以下场景&#xff1a; 将所有的数据都作为训练数据&#xff0c;训练出一个模型直接上线预测 每当得到一个新的数据&#xff0c;则计算新数据到训练数据的距离&#xff0c;预测得到新数据的类别 存在问题&…

#前后端分离# 头条发布系统

头条业务简介 用户功能 注册功能登录功能jwt实现 新闻 新闻的分页浏览通过标题关键字搜索新闻查看新闻详情新闻的修改和删除 预览界面 开源上线 https://gitcode.net/NVG_Haru/NodeJS_5161447 数据库设计 数据库脚本 CREATE DATABASE sm_db;USE sm_db;SET NAMES utf8mb4…

Abstract Factory抽象工厂模式(对象创建)

抽象工厂模式&#xff1a;Abstract Factory 链接&#xff1a;抽象工厂模式实例代码 解析 目的 在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象工作”&#xff1b;同时&#xff0c;由于需求的变化&#xff0c;往往存在更多系列对象的创建工作。 如何应对这种变化…

基于Javaee的影视创作论坛的设计与实现+vue论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装影视创作论坛软件来发挥其高效地信息处理的作用&#xff0c…

Typora快捷键设置详细教程

文章目录 一、快捷键设置步骤二、设置快捷键简单案例参考资料 一、快捷键设置步骤 在typora软件中&#xff0c;快捷键的设置步骤主要为&#xff1a; 打开【文件】–>【偏好设置】&#xff0c;找到【通用】–>【打开高级设置】&#xff0c;找到 conf.user.json 文件。 然…

c++哈希表——超实用的数据结构

文章目录 1. 概念引入1.1 整数哈希1.1.1 直接取余法。1.1.2 哈希冲突1.1.2.1 开放寻址法1.1.2.2 拉链法 1.2 字符串哈希 3.结语 1. 概念引入 哈希表是一种高效的数据结构 。 H a s h Hash Hash表又称为散列表&#xff0c;一般由 H a s h Hash Hash函数(散列函数)与链表结构共同…

安全生产知识竞赛活动方案

为进一步普及安全生产法律法规知识&#xff0c;增强安全意识&#xff0c;提高安全技能&#xff0c;经研究&#xff0c;决定举办以“加强安全法治、保障安全生产”为主题的新修订《安全生产法》知识竞赛活动&#xff0c;现将有关事项通知如下&#xff1a; 一、活动时间&#xf…

【网络安全】网络隔离设备

一、网络和终端隔离产品 网络和终端隔离产品分为终端隔离产品和网络隔离产品两大类。终端隔离产品一般指隔离卡或者隔离计算机。网络隔离产品根据产品形态和功能上的不同&#xff0c;该类产品可以分为协议转换产品、网闸和网络单向导入产品三种。 图1为终端隔离产品的一个典型…

HTML5+CSS3②——图像、超链接、音频、视频

目录 图像 超链接 音频 视频 图像 作用&#xff1a;在网页中插入图片 单标签&#xff1a; 标签名&#xff1a;<img src"图片的URL"> <img src"图片的URL" alt"替换文本" title"提示文本"> 属性写在尖括号里面&#xff0c;…

WPF+Halcon 培训项目实战(12):WPF导出匹配模板

文章目录 前言相关链接项目专栏运行环境匹配图片WPF导出匹配模板如何了解Halcon和C#代码的对应关系逻辑分析&#xff1a;添加截取ROI功能基类矩形圆形 生成导出模板运行结果&#xff1a;可能的报错你的文件路径不存在你选择的区域的内容有效信息过少 前言 为了更好地去学习WPF…