STM32系列定时器

本文详细介绍了STM32微控制器中定时器的工作原理,包括SysTick滴答定时器的配置与使用,软件定时器的实现,以及硬件定时器如TIM3的PWM输出和输入捕获功能。同时,还探讨了高级定时器TIM1和TIM8的PWM输出特性,以及通用定时器的输入捕获和编码器解码功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SysTick滴答定时器

  • 滴答定时器介绍
    SysTick滴答定时器是一个24 位的递减计数器,支持中断,SysTick 定时器被捆绑在 NVIC 中,用于产生 SYSTICK 异常(异常号:15)
  • 滴答定时器时钟源的选择函数
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}
  • 滴答定时器的配置
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value impossible */
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = ticks - 1; /* set reload register */
/* set Priority for Systick Interrupt */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);//优先级设置为15(系统最低优先级)
/* Load the SysTick Counter Value */
SysTick->VAL = 0;
/* Enable SysTick IRQ and SysTick Timer */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
/* Function successful */
return (0);
}
  • 配置SysTick中断,并初始化软件定时器变量
void bsp_InitTimer(void)
{
	uint8_t i;
	/*清零所有的软件定时器 */
	for (i = 0; i < TMR_COUNT; i++)
	{
		s_tTmr[i].Count = 0;
		s_tTmr[i].PreLoad = 0;
		s_tTmr[i].Flag = 0;
		s_tTmr[i].Mode = TMR_ONCE_MODE;	/* ȱʡÊÇ1´ÎÐÔ¹¤×÷ģʽ */
	}
		/*配置systick中断周期为1ms,并启动systick中断
    	SystemCoreClock 是固件库定义的系统内核时钟,对于STM32F4XX,一般为 168MHz
    	SysTick_Config()函数的形参表示内核时钟多少个周期后触发一次Systick定时中断
	  	-- SystemCoreClock / 1000  表示定时频率为1000HZ,定时周期为1ms
	    -- SystemCoreClock / 500   表示定时频率为500HZ,定时周期为2ms
	    -- SystemCoreClock / 2000  表示定时频率为2000HZ,定时周期为500us   */
	SysTick_Config(SystemCoreClock / 1000);
//	bsp_InitHardTimer();	/*开启硬件定时中断 */
}

软件定时器

  • 启动自动定时器
void bsp_StartAutoTimer(uint8_t _id, uint32_t _period)
{
	if (_id >= TMR_COUNT)
	{
		/* 打印出错的源代码文件名、函数名称 */
		BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, __FUNCTION__);
		while(1); /* 参数异常,死机等待看门狗复位 */
	}
	DISABLE_INT(); /* 关中断 */
	s_tTmr[_id].Count = _period;  /* 实时计数器初值 */
	s_tTmr[_id].PreLoad = _period;  /* 计数器自动重装值,仅自动模式起作用 */
	s_tTmr[_id].Flag = 0; /* 定时时间到标志 */
	s_tTmr[_id].Mode = TMR_AUTO_MODE;  /* 自动工作模式 */
	ENABLE_INT(); /* 开中断 */
}
  • 函数递减计数器
/******************************************************************************
*  函 数 名: bsp_SoftTimerDec
*  功能说明: 每隔1ms对所有定时器变量减1。必须被SysTick_ISR周期性调用。
*  形 参: _tmr : 定时器变量指针
*  返 回 值: 无
******************************************************************************/
static void bsp_SoftTimerDec(SOFT_TMR *_tmr)
{
	if (_tmr->Count > 0)
	{
	/* 如果定时器变量减到1则设置定时器到达标志 */
		if (--_tmr->Count == 0)
		{
			_tmr->Flag = 1;
			/* 如果是自动模式,则自动重装计数器 */
			if(_tmr->Mode == TMR_AUTO_MODE)
			{
				_tmr->Count = _tmr->PreLoad;
			}
		}
	}
}
  • 超时检测函数
uint8_t bsp_CheckTimer(uint8_t _id)
	{
		if (_id >= TMR_COUNT)
		{
			return 0;
		}
		if (s_tTmr[_id].Flag == 1)
		{
			s_tTmr[_id].Flag = 0;
			return 1;
		}
		else
		{
			return 0;
	}
}
  • 停止软件定时器
void bsp_StopTimer(uint8_t _id)
{
	if (_id >= TMR_COUNT)
	{
	/* 打印出错的源代码文件名、函数名称 */
		BSP_Printf("Error: file %s, function %s()\r\n", __FILE__, 	__FUNCTION__);
		while(1); /* 参数异常,死机等待看门狗复位 */
	}
	DISABLE_INT(); /* 关中断 */
	s_tTmr[_id].Count = 0; /* 实时计数器初值 */
	s_tTmr[_id].Flag = 0; /* 定时时间到标志 */
	s_tTmr[_id].Mode = TMR_ONCE_MODE;  /* 自动工作模式 */
	ENABLE_INT(); /* 开中断 */
}

定时器中断

  • 控制寄存器1(TIMx_CR1)在这里插入图片描述

  • 自动重装载寄存器(TIMx_ARR)
    在这里插入图片描述
    该寄存器在物理上实际对应着 2 个寄存器:
    当TIMx_CR1中APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2 个寄存器是连通的;
    当TIMx_CR1中APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。

  • DMA/中断使能寄存器(TIMx_DIER)
    在这里插入图片描述

  • 预分频寄存器(TIMx_PSC)—该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
    在这里插入图片描述

  • 状态寄存器(TIMx_SR)—该寄存器用来标记当前与定时器相关的各种事件/中断是否发生
    在这里插入图片描述

  • 初始化

void TIM3_Int_Init(u16 arr,u16 psc)
{
	RCC->APB1ENR|=1<<1;  //TIM3 时钟使能
	TIM3->ARR=arr; //设定计数器自动重装值//刚好 1ms
	TIM3->PSC=psc; //预分频器 7200,得到 10Khz 的计数时钟
	TIM3->DIER|=1<<0; //允许更新中断 
	TIM3->CR1|=0x01; //使能定时器 3
	MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占 1,子优先级 3,组 2 
} 
  • 定时器中断服务
void TIM3_IRQHandler(void)
{
	if(TIM3->SR&0X0001) //溢出中断
	{
	
	} 
	TIM3->SR&=~(1<<0); //清除中断标志位
}

PWM

  • 简介
    STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出。
  • 捕获/比较模式寄存器(TIMx_CCMR1/2)
    TIMx_CCMR1 控制 CH1(低八位) 和 2(高八位),而 TIMx_CCMR2 控制 CH3 和 4
    在这里插入图片描述
    位OCxM可配置成7种模式,PWM模式对应的设置为 110/111。
    位CCxS 用于设置通道的方向(输入/输出)。
  • 捕获/比较使能寄存器(TIMx_CCER)—控制着各个输入输出通道的开关
    在这里插入图片描述
  • TIMx_BDTR
    高级定时器要想输出 PWM,必须还要设置一个MOE 位(TIMx_BDTR 的第 15 位),以使能主输出,否则不会输出 PWM。
  • 初始化
void TIM3_PWM_Init(u16 arr,u16 psc)
{ 	
	//arr:自动重装值		psc:时钟预分频数
	RCC->APB1ENR|=1<<1; 	//TIM3 时钟使能
	RCC->APB2ENR|=1<<3; 	//使能 PORTB 时钟 
	GPIOB->CRL&=0XFF0FFFFF; //PB5 输出
	GPIOB->CRL|=0X00B00000; //复用功能输出
	RCC->APB2ENR|=1<<0; 	//开启辅助时钟 
	AFIO->MAPR&=0XFFFFF3FF; //清除 MAPR 的[11:10]
	AFIO->MAPR|=1<<11; 		//部分重映像,TIM3_CH2->PB5
	TIM3->ARR=arr; 			//设定计数器自动重装值
	TIM3->PSC=psc; 			//预分频器不分频
	TIM3->CCMR1|=7<<12;//CH2 PWM2 模式
	TIM3->CCMR1|=1<<11;//CH2 预装载使能
	TIM3->CCER|=1<<4;  //OC2 输出使能 
	TIM3->CR1=0x0080;  //ARPE 使能
	TIM3->CR1|=0x01;   //使能定时器 3
}

输入捕获

在这里插入图片描述
STM32F1 的定时器除了 TIM6 和 TIM7都具有输入捕获功能。
STM32F1 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。

  • TIMx_CCMR1 [7:0]位详细描述在这里插入图片描述
  • TIMx_CCER 最低 2 位描述
    在这里插入图片描述
  • 捕获/比较寄存器(TIMx_CCR1~4)。
    在这里插入图片描述
    该寄存器用来存储捕获发生时,TIMx_CNT的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度。
  • 捕获高电平脉宽的思路
    首先,设置 TIM5_CH1 捕获上升沿,这在TIM5_Cap_Init 函数执行的时候就设置好了,然后等待上升沿中断到来,当捕获到上升沿中断,此时如果 TIM5CH1_CAPTURE_STA 的第 6 位为 0,则表示还没有捕获到新的上升沿,就先把TIM5CH1_CAPTURE_STA、TIM5CH1_CAPTURE_VAL 和 TIM5->CNT 等清零,然后再设置IM5CH1_CAPTURE_STA 的第 6 位为 1,标记捕获到高平,最后设置为下降沿捕获,等待下降沿到来。如果等待下降沿到来期间,定时器发生了溢出,就在 TIM5CH1_CAPTURE_STA里面对溢出次数进行计数,当最大溢出次数来到的时候,就强制标记捕获完成(虽然此时还没有捕获到下降沿)。当下降沿到来的时候,先设置 TIM5CH1_CAPTURE_STA 的第 7 位为 1,标记成功捕获一次高电平,然后读取此时的定时器值到 TIM5CH1_CAPTURE_VAL 里面,最后设置为上升沿捕获,回到初始状态。这样就完成了一次高电平捕获。
  • 定时器 5 通道 1 输入捕获库函数配置
TIM_ICInitTypeDef TIM5_ICInitStructure;
void TIM5_Cap_Init(u16 arr,u16 psc)
{ 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //①使能 TIM5 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //①使能 GPIOA 时钟
	//初始化 GPIOA.0 ①
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 设置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);  //PA0 下拉
	//②初始化 TIM5 参数
	TIM_TimeBaseStructure.TIM_Period = arr; //设定计数器自动重装值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频器
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //初始化 TIMx
	//③初始化 TIM5 输入捕获通道 1
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1 映射到 TI1 上
	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  //上升沿捕获
	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到 TI1 上
	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
	TIM5_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器 不滤波
	TIM_ICInit(TIM5, &TIM5_ICInitStructure); //初始化 TIM5 输入捕获通道 1
	//⑤初始化 NVIC 中断优先级分组
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3 中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级 2 级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级 0 级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
	NVIC_Init(&NVIC_InitStructure); //初始化 NVIC
	TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//④允许更新中断捕获中断
	TIM_Cmd(TIM5,ENABLE ); //⑥使能定时器 5
}

u8 TIM5CH1_CAPTURE_STA=0;  //自定义输入捕获状态
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//⑤定时器 5 中断服务程序
void TIM5_IRQHandler(void)
{
	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
	{ 
		if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
		{ 
			if(TIM5CH1_CAPTURE_STA&0X40) //已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0X80; //标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0XFFFF;
				}else TIM5CH1_CAPTURE_STA++;
			} 
		}
    if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
	{ 
		if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
		{ 
			TIM5CH1_CAPTURE_STA|=0X80;  //标记成功捕获到一次上升沿
			TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获(修改输入捕获通道1的极性)
		}else //还未开始,第一次捕获上升沿
		{
			TIM5CH1_CAPTURE_STA=0;  //清空
			TIM5CH1_CAPTURE_VAL=0;
			TIM_SetCounter(TIM5,0); //设置计数器寄存器值
			TIM5CH1_CAPTURE_STA|=0X40;  //标记捕获到了上升沿
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
		} 
	 } 
 }
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

Note:TIM5CH1_CAPTURE_STA,是用来记录捕获状态,本身就是个变量,只是我们把它当成一个寄存器那样来使用)。TIM5CH1_CAPTURE_STA 各位描述如图所示:
在这里插入图片描述

  • 定时器 5 通道 1 输入捕获寄存器操作配置
void TIM5_Cap_Init(u16 arr,u16 psc)
{ 
	RCC->APB1ENR|=1<<3; //TIM5 时钟使能
	RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
	GPIOA->CRL&=0XFFFFFFF0; //PA0 清除之前设置
	GPIOA->CRL|=0X00000008;  //PA0 输入
	GPIOA->ODR|=0<<0; //PA0 下拉 
	TIM5->ARR=arr; //设定计数器自动重装值
	TIM5->PSC=psc; //预分频器
	TIM5->CCMR1|=1<<0; //CC1S=01 选择输入端 IC1 映射到 TI1 上
	TIM5->CCMR1|=0<<4; //IC1F=0000 配置输入滤波器 不滤波
	TIM5->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频
	TIM5->CCER|=0<<1; //CC1P=0  上升沿捕获
	TIM5->CCER|=1<<0; //CC1E=1 允许捕获计数器的值到捕获寄存器中
	TIM5->DIER|=1<<1; //允许捕获中断 
	TIM5->DIER|=1<<0; //允许更新中断 
	TIM5->CR1|=0x01; //使能定时器 2
	MY_NVIC_Init(2,0,TIM5_IRQn,2);//抢占 2,子优先级 0,组 2 
}
u8 TIM5CH1_CAPTURE_STA=0;  //输入捕获状态 
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
//定时器 5 中断服务程序 
void TIM5_IRQHandler(void)
{
	u16 tsr;
	tsr=TIM5->SR;
	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获 
	{
		if(tsr&0X01)//溢出
		{ 
			if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0XFFFF;
				}else TIM5CH1_CAPTURE_STA++;
			} 
		 }
		if(tsr&0x02)//捕获 1 发生捕获事件
		{ 
			if(TIM5CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
			{ 
				TIM5CH1_CAPTURE_STA|=0X80;  //标记成功捕获到一次高电平脉宽
				TIM5CH1_CAPTURE_VAL=TIM5->CCR1;//获取当前的捕获值.
				TIM5->CCER&=~(1<<1);  //CC1P=0 设置为上升沿捕获
			}else //还未开始,第一次捕获上升沿
			{
				TIM5CH1_CAPTURE_STA=0;  //清空
				TIM5CH1_CAPTURE_VAL=0;
				TIM5CH1_CAPTURE_STA|=0X40;  //标记捕获到了上升沿
				TIM5->CNT=0;  //计数器清空
				TIM5->CCER|=1<<1; //CC1P=1 设置为下降沿捕获
			} 
		} 
	}
	TIM5->SR=0;//清除中断标志位
}
  • 编码器转动速度
    定时器有些通道具有AB正交解码功能
static void Encoder_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure;   	
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_TIM4,ENABLE );//重映射
/*
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	// 正交编码器输入引脚  PB->6   PB->7 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;         
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);                           
*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
	// 正交编码器映射引脚 PD->12   PD->13 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;         
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &GPIO_InitStructure);   
	
	/*- TIM4编码器模式配置 -*/
	TIM_DeInit(TIM4);
	TIM_TimeBaseStructure.TIM_Period = 60000;									//设定计数器重装载值
	TIM_TimeBaseStructure.TIM_Prescaler = 0;								//50Hz采样编码
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
  
	TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI1, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising);	//配置编码器模式触发源和极性
	TIM_ICStructInit(&TIM_ICInitStructure);																																//配置滤波器
	TIM_ICInitStructure.TIM_ICFilter = 6;
	TIM_ICInit(TIM4, &TIM_ICInitStructure);

	TIM4->CNT = 0;
	TIM_ClearFlag(TIM4, TIM_FLAG_Update);
	TIM_Cmd(TIM4, ENABLE);   //启动TIM4定时器
}
encoder_dcar = TIM4->CNT;
TIM4->CNT = 0;
SENSOR_DATA.encoder_delta_car += (encoder_dcar > 30000)?(encoder_dcar-60000):encoder_dcar;		//正交解码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值