【理论】STM32定时器时间计算公式 +【实践】TIM中断1s计时一次——HAL库和标准库程序举例

 前言:定时器TIM的详细知识点见我的博文:11.TIM定时中断-CSDN博客

STM32定时器时间计算公式


公式解释:

ARR(TIM_Period):自动重装载值,是定时器溢出前的计数值

PSC(TIM_Prescaler):预分频值,是用来降低定时器时钟频率的参数

Tclk:定时器的输入时钟频率(单位Mhz),通常为系统时钟频率或者定时器外部时钟频率

Tout:定时器溢出时间(单位us)。一定要注意这个单位是us

公式由来:

1.定时器的时钟频率是Tclk,TIM_Prescaler即为PSC的值。时钟频率被分频了PSC+1,那么此时定时器的最终频率为,故可知定时器计数值加1所需的时间为

注:时间等于频率的倒数

2.自动重装载值即TIM_Period即ARR,定时器从0计数到ARR时清零。由第一步已经计算出了被分频了PSC+1的最终定时器的时钟频率为,这是计数一次的频率,则计数到ARR的时间为 为(ARR + 1) /  (时间等于频率的倒数),故定时器溢出时间(单位us)为Tout=((ARR+1)*(PSC+1)) / Tclk。


理论联系实际,来加深理解,接下来使用STM32CubeMx + Keil来实现TIM中断实现1s计时一次。

TIM中断实现1s计时一次

前言:使用的是STM32f103c8t6,系统主频72Mhz

目标:实现TIM中断实现1s计时一次

主要过程:配置定时器溢出时间为10ms(即定时器计数一次10ms,也就是10ms的定时器中断),当计次100次时是1s(1000ms),进而通过置标志位来实现1s的其它操作。

STM32CubeMx  HAL库程序举例

1.在STM32CubeMx中选择TIM2,设置Period(ARR)为7200,设置Prescaler(PSC)为100,根据公式计算得定时器溢出时间即定时器的中断时间(单位us)为\frac{7200\times100 }{72}, 最后结果为10 000 us,即10ms。

注意:下图中的Prescaler(PSC)设置为100-1

对应的代码以及具体配置如下所示(HAL库版本),这段代码是一个使用TIM2定时器进行初始化配置的函数。

具体配置如下:

设置TIM2的时钟源配置为默认值。

设置TIM2的主配置为默认值。

对htim2即TIM_HandleTypeDef类型的结构体变量进行初始化配置:设置htim2的实例为TIM2。

设置htim2的预分频器为7200-1,这将把输入时钟频率除以7200来得到TIM2的时钟频率。

设置htim2的计数模式为向上计数模式TIM_COUNTERMODE_UP。 

设置htim2的计数器周期为100,这意味着当计数器达到100时,将发生定时器事件(溢出或中断)。

设置htim2的时钟分频因子为TIM_CLOCKDIVISION_DIV1即无时钟分频。

禁用htim2的自动重装载预装载功能TIM_AUTORELOAD_PRELOAD_DISABLE。这意味着在更新事件时,直接将新的周期值加载到计数器。

void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 7200-1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 100-1;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
}

2.写定时器2中断服务函数,10ms一次中断。这段代码是在定时器2的周期到达时触发的回调函数。在每次定时器2的周期到达时,回调函数`HAL_TIM_PeriodElapsedCallback()`会被调用。代码以及具体流程如下。

具体代码流程如下:

首先判断触发回调函数的定时器实例是否是htim2。如果是htim2实例,即定时器2的周期到达,进入下一步。
`index_10ms`变量自增1,表示经过了10毫秒。
如果`index_10ms`变量的值能够被100整除(即经过了1秒),则将`index_led`变量设置为1。

这段代码的作用是,每隔10毫秒触发一次定时器2的中断服务函数。通过`index_10ms`变量来计数,当计数到100时(经过1秒),将`index_led`变量置为1。

在实际应用中,可以根据`index_led`变量的值来控制相关的LED灯或者执行其他操作,实现定时任务的触发和事件响应。

static uint16_t index_10ms = 0;
uint16_t index_led = 0;

/**
  * @brief          定时器2中断服务函数,10ms一次中断
  * @param[in]      htim:定时器
  * @retval         none
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)	
{
	if (htim->Instance == htim2.Instance)
	{
		index_10ms++;	 
    if(index_10ms%100==0)
    {
      index_led=1;
    }
	}
}

3.利用定时器中断来写你自己定义的功能函数。我写的功能函数是实现1s打印一次hello,word。

这段代码其中的逻辑是通过检测外部定义的`index_led`变量的值来执行特定的操作。代码以及具体流程如下。

具体代码流程如下:

- 当`index_led`变量的值为1时,执行以下操作:
  - 打印输出"hello,world"字符串。
  - 将`index_led`变量的值重新设置为0,表示已经处理过这次触发。

这段程序逻辑的作用是在每次`index_led`变量变为1时,打印输出"hello,world"字符串,并且只执行一次,直到下次`index_led`又变为1。

extern uint16_t index_led;
uint8_t led_status =0;
/**
  * @brief          自定义功能函数
  * @param[in]      none
  * @retval         none
  */
void user(void)
{
  if(index_led==1)
  {
    printf("hello,world\r\n");
    index_led=0;
  }
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}

标准库程序举例

Timer.c文件

#include "stm32f10x.h"                  // Device header
#include "sys.h"
/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 7199;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 99;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}


使用void TIM2_IRQHandler(void)函数 

	void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		index_10ms++;	 
    if(index_10ms%100==0)
    {
      shuayadaojishi--;
    }
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

 

用以下代码串口一直为0#include "stm32f10x.h" volatile uint32_t pulse_count = 0; float flow_rate = 0.0; void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { pulse_count++; TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } } void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // USART1_TX PA9 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct) // USART1_RX PA10 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStruct); USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStruct); USART_Cmd(USART1, ENABLE); } void send_data(float flow) { char buffer[50]; int length = sprintf(buffer, "Flow: %.2f L/min\r\n", flow); for(int i=0; i<length; i++) { USART_SendData(USART1, buffer[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } } int main(void) { SystemInit(); USART1_Init(); NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); while(1) { pulse_count = 0; TIM2->CNT = 0; Delay_ms(1000); // 1秒采样周期
最新发布
03-26
<think>好的,我现在需要解决用户在STM32F10x上遇到的串口通信问题,具体是脉冲计数始终为0,可能涉及到TIM2定时器USART1中断配置问题。首先,我得回忆一下STM32的相关知识,尤其是定时器串口的配置步骤,以及常见的问题点。 用户提到脉冲计数为0,这可能意味着定时器没有正确初始化,或者中断没有正常触发,导致计数器无法累加。另外,USART1的问题可能涉及通信配置错误,导致数据无法发送或接收。我需要逐步分析可能的原因。 首先,检查定时器TIM2的配置。根据用户提供的引用[1],定时器初始化需要设置预分频器(PSC)自动重装载寄存器(ARR)的值。如果这两个参数设置不当,定时器的时钟频率可能不符合预期,导致计数器不工作。例如,假设用户设置的预分频器为7999,ARR为9999,这样定时器的周期应该是(7999+1)*(9999+1)/时钟源频率。如果时钟源是72MHz,则周期为(8000*10000)/72MHz≈1.111秒,这样的配置可能适用于较慢的脉冲计数,但如果脉冲频率较高,可能会溢出或者无法正确捕捉。不过用户的代码中TIM2的初始化是否正确呢?需要确认是否调用了HAL_TIM_Base_Start或相关的启动函数,以及是否启用了中断。 其次,脉冲计数可能涉及到输入捕获功能。用户是否配置了TIM2的输入捕获通道?例如,使用TIM_ICInitTypeDef结构体配置输入捕获参数,比如触发边沿(上升沿或下降沿)、预分频器、滤波器等。如果输入信号没有正确连接到定时器的通道,或者捕获配置错误,可能导致无法计数。 另外,中断配置是否正确也很关键。定时器的输入捕获中断需要使能,并且NVIC中对应的中断通道需要配置优先级并启用。用户是否在初始化代码中调用了HAL_TIM_IC_Start_IT函数来启动输入捕获并开启中断?如果没有,中断服务函数不会被触发,计数器也就不会更新。 再看USART1的问题。用户提到串口通信问题,可能是配置错误导致数据无法发送或接收。需要检查USART1的波特率、数据位、停止位、校验位等参数是否与对方设备匹配。此外,GPIO引脚是否正确配置为复用推挽输出(对于TX)输入(对于RX)?时钟是否使能了USART1对应的GPIO端口? 中断方面,USART1中断是否启用?例如,接收中断是否开启,对应的NVIC配置是否正确。如果使用DMA传输,还需要检查DMA配置是否正确,缓冲区是否设置妥当。 另外,用户的代码中是否有处理中断服务函数?例如,TIM2的中断服务函数是否正确定义,并且在其中清除中断标志,更新计数值?如果中断处理函数没有正确实现,或者没有清除中断标志,会导致后续中断无法触发,计数器无法增加。 还有可能的问题包括时钟源配置错误。例如,如果TIM2的时钟源没有正确使能,或者时钟分频设置错误,导致定时器实际没有时钟输入,自然不会计数。需要确认系统时钟配置是否正确,尤其是APB1总线的时钟,因为TIM2挂载在APB1上。 此外,硬件连接问题也可能导致脉冲计数为0。例如,输入的脉冲信号是否确实到达了TIM2的对应引脚?是否接触不良或者线路连接错误?使用示波器或逻辑分析仪检查信号是否存在,以及是否符合预期的电平频率。 对于USART1,如果发送或接收的数据始终为零,可能是发送函数未被调用,或者缓冲区未正确填充数据。需要检查发送部分的代码,是否有调用HAL_UART_Transmit或类似的函数,并传递正确的数据长度。同时,检查是否启用了USART的发送或接收中断,并在中断处理函数中正确操作数据。 综合上述可能的原因,解决步骤可能包括: 1. 检查TIM2的初始化代码,确认预分频器ARR值设置正确,时钟源选择正确。 2. 确认TIM2的输入捕获通道配置正确,包括边沿检测滤波器设置。 3. 确保启用了TIM2的中断,并在NVIC中配置了优先级。 4. 在中断服务函数中正确处理捕获事件,更新计数值并清除中断标志。 5. 检查USART1的配置参数,确保波特率等与通信对方一致。 6. 确认USART1中断或DMA配置正确,并处理接收/发送数据。 7. 使用调试工具(如ST-Link)逐步调试,查看寄存器的值是否符合预期,中断是否触发。 8. 检查硬件连接,确保信号正确接入对应引脚。 可能还需要参考用户提供的引用内容,例如引用[1]中的定时器初始化示例,确认用户代码是否遗漏了某些配置步骤,比如是否启动定时器,是否启用自动重载等。同时,引用[3]中的代码片段显示调用了TIM1_Int_Init函数,可能涉及中断配置,需要对比用户自己的TIM2初始化是否有类似的中断使能步骤。 最后,需要生成相关问题,帮助用户进一步排查问题,比如检查定时器时钟是否使能,检查GPIO复用配置是否正确,检查中断优先级冲突等。</think>### 解决STM32串口通信及TIM2脉冲计数为0的问题 #### 1. **定时器TIM2配置检查** - **时钟源与分频器** 确保TIM2的时钟源已启用(APB1总线),且预分频器(PSC)自动重装载寄存器(ARR)设置正确。例如,若时钟源为72MHz,配置`PSC=7999``ARR=9999`时,定时器周期为: $$ T = \frac{(PSC+1) \times (ARR+1)}{F_{clock}} = \frac{8000 \times 10000}{72 \times 10^6} \approx 1.111 \text{秒} $$ 若需要更快的计数频率,需调整参数[^1]。 - **输入捕获模式** 如果脉冲计数使用输入捕获功能,需配置TIM2的通道为输入捕获模式(如通道1),并设置触发边沿(上升沿/下降沿)。示例代码片段: ```c TIM_IC_InitTypeDef sConfigIC; sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿触发 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 启动输入捕获并启用中断 ``` #### 2. **中断配置** - **NVIC设置** 在`HAL_TIM_Base_Init`后,需启用TIM2全局中断并配置优先级: ```c HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); ``` - **中断服务函数** 确保中断服务函数`TIM2_IRQHandler`中清除中断标志并处理计数逻辑: ```c void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC1); pulse_count++; // 更新计数器 } } ``` #### 3. **USART1通信问题排查** - **GPIO与时钟配置** 确认USART1的TX(PA9)RX(PA10)引脚已配置为复用推挽输出浮空输入,且APB2时钟使能: ```c __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART1_CLK_ENABLE(); ``` - **波特率与模式** 检查波特率是否与设备一致(如115200),并启用接收中断: ```c huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1); HAL_UART_Receive_IT(&huart1, rx_buffer, 1); // 启用接收中断 ``` #### 4. **硬件检查** - **信号输入路径** 使用示波器检查脉冲信号是否到达TIM2的输入引脚(如PA0对应TIM2_CH1)。 - **电平匹配** 确认信号电平符合STM32要求(3.3V),过高需分压,过低需放大。 #### 5. **调试工具辅助** - **寄存器查看** 通过STM32CubeIDE调试模式,检查TIM2的CR1寄存器(是否使能)、SR寄存器(中断标志是否触发)。 - **断点测试** 在中断服务函数中设置断点,验证是否进入中断。 --- ###
评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿齐Archie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值