STM32学习及开发笔记八:采用主从计时器实现精确脉冲输出

本文介绍了一种利用STM32的主从计时器实现精确脉冲数量输出的方法,详细解释了配置过程及代码实现,适用于需要精确控制运动或信号的场景。

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

脉冲信号用于设备控制是非常常见的,但在一些情况下,我们希望精确的控制脉冲的数量以实现对运动的精确控制。实现的方式也许有多种多样,但使用计时器来实现此类操作是人们比较容易想到的。

1、原理概述

我们知道在STM32平台上,使用计时器来实现PWM操作是非常常见的用法。使用的是单一计时器,事实上通过主从两个计时器配合我们也可通过生成PWM波的方式精确控制输出脉冲的数量。接下来我们就来简单了解一下使用主从计时器实现精确数量脉冲输出的原理。

对于STM32平台一般都有TIM1和TIM8两个高级定时器和TIM2、TIM3、TIM、TIM5等几个通用定时器。STM32的这些定时器可以通过另外一个定时器的某一个条件被触发而启动。这里所谓某一个条件可以是定时到时、定时器超时、比较成功等各种条件。这种通过一个定时器触发另一个定时器的工作方式称为定时器的同步,发出触发信号的定时器工作于主模式,接受触发信号而启动的定时器工作于从模式。这些个计时器都可用作从计时器,但作为主计时器则是对应不同的触发源,它们的主从关系必须遵循设定不可随意配置。具体的配置关系如下:

当然要实现精确控制脉冲输出,就需要按照上述列表中的要求实现主从计时器的配置。对于主计时器来说,要将输出配置为PWM输出,并将触发输出的主从模式启用。而对于从计时器来说,需要启用从模式,并设为门控方式,触发源则根据上述表中的描述来选择。

可是为什么主从计时器就能实现精确数量的脉冲输出呢?我们借助下面的简单图示来说明这个问题。

首先按前面所述的主从计时器要求配置好主从计时器,这是最基本的要求。主计时器负责设置脉冲输出的频率以及输出脉冲,从计数器所控制输出的脉冲数。具体过程是这样的,主进程启动主从计时器,从计时器通过主计时器输出的触发信号开始脉冲计数,当达到指定的计数值后,产生中断停止主计时器输出,直到主进程再次开启这一过程。

2、系统设计

我们已经了解了通过主从计时器实现精确数量脉冲输出的基本原理。那究竟如何实际做呢?接下来我们就设计一个简单的系统实现它。

在这一系统中,我是使用STM32F407作为实现平台,以TIM1作为主计时器,TIM4作为从计时器,同时输出四路脉冲信号。四路的频率是相同的,但每一路的输出数量是可以设定的。具体的操作结构如下图所示:

主进程轮询控制计时器TIM1和TIM4工作,而TIM1主计时器给TIM4从计时器输出触发信号,而从计时器到达指定脉冲数后输出中断信号控制TIM1的输出通道停止。我们人为规定TIM4的通道1、2、3、4与TIM1的输出通道1、2、3、4相对应。

3、代码实现

我们已经说明了使用主从计时器实现精确输出脉冲数的原理,也设计了我们的我们想要实现的系统结构,接下来我们实现这一系统。

3.1、主计时器的配置

首先我们来看一看主计时器的配置,具体代码如下:

/*TIM1初始化配置*/
static void TIM1_Init_Configuration(uint32_t period)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = (period-1);
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = (period/2);
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim1);
}

3.2、从计时器的配置

接着我们再来看一看从计时器的配置,具体代码如下:

/*TIM4初始化配置*/
static void TIM4_Init_Configuration(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 0xFFFF;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchronization(&htim4, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

3.3、主轮询函数实现

主轮询函数控制着主从计时器的启动,是实现脉冲输出的控制者,包括设置脉冲数并开启从计数器的计数和中断以及启动主计时器的输出。具体代码如下:

/*实现通讯数据的处理*/

void HgraDataProcess(void)
{
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);  // 捕获比较1中断使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING);  // 捕获比较2中断使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_3,TIM_ICPOLARITY_RISING);  // 捕获比较3中断使能
  TIM_SET_CAPTUREPOLARITY(&htim1,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING);  // 捕获比较4中断使能
 
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,6400);     // 输入通道1的捕获比较值CCR1
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_2,6400);     // 输入通道2的捕获比较值CCR2
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_3,6400);     // 输入通道3的捕获比较值CCR3
  __HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_4,6400);     // 输入通道4的捕获比较值CCR4

  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_1);    //开启定时器4通道1的输入捕获中断
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_2);    //开启定时器4通道2的输入捕获中断
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_3);    //开启定时器4通道3的输入捕获中断
  HAL_TIM_OC_Start_IT(&htim4,TIM_CHANNEL_4);    //开启定时器4通道4的输入捕获中断

  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);  //开启定时器1通道1的PWM输出中断        
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_2);  //开启定时器1通道2的PWM输出中断
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3);  //开启定时器1通道3的PWM输出中断
  HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_4);  //开启定时器1通道4的PWM输出中断
}

3.4、中断处理函数的实现

从计时器产生中断后,会根据不同的中断调用不同的中断处理函数,这些回调函数是需要我们实现的,在这里要实现主计时器PWM输出的停止以及中断标志的复位等处理。具体实现代码如下:

/*PWM中断轮询回调函数*/
static void TIM1_PWM_PulseFinished(TIM_HandleTypeDef *htim)
{
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)                         //判断是否生成中断标志位SR
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) !=RESET)              //定时器中断使能是否开启
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC1);                   //清除中断标志位SR
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_1)==HAL_OK)         //关闭定时器1的通道1的PWM输出
      {
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_1) ;                   //关闭定时器4的通道1的输入中断捕获
        flagStop[0] = 1;                                                //关闭标志置1
      }
    }
  }                                                                          //下面的通道2同理如此
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC2);                       //清除标志位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_2)==HAL_OK)
      {    a
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_2) ;   
        flagStop[1] = 1;           
      }
    }
  }
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC3);                       //清除标志位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_3)==HAL_OK)
      {    
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_3) ;   
        flagStop[2] = 1;           
      }
    }
  }
  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET)
  {
    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) !=RESET)
    {
      __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_CC4);                       //清除标志位
     
      if(HAL_TIM_PWM_Stop_IT(&htim1, TIM_CHANNEL_4)==HAL_OK)
      {    
        HAL_TIM_OC_Stop_IT(&htim4,TIM_CHANNEL_4) ;   
        flagStop[3] = 1;            
      }
    }
  }
   
  if((flagStop[0]== 1)&&(flagStop[1] == 1)&&(flagStop[2] == 1)&&(flagStop[3] == 1))
  {
    flagStop[0]= 0;
    flagStop[1]= 0;
    flagStop[2]= 0;
    flagStop[3]= 0;

    __HAL_TIM_SET_COUNTER(&htim4,0);
  }
}

4、小结

我们设计了一个四路输出的脉冲输出,每一路的输出数量可以精确单独控制,在输出的频率相对较低而且数量不大的情况下我们验证是没有问题的。当然在数量特别多时,是否有偏差我们没有测试。而在我们使用的平台,时钟为168MHz,根据我们的简单测试在输出8MHz的脉冲时还是比较精确的,不过这已经完全满足一般的应用需求。

其实从STM32的手册我可以知道,输出指定脉冲数的方法有多种,但使用主从计时器方式是比较好的一种。这种方式虽然多用了一个定时器,但因为不需要频繁中断大大减少了CPU的处理资源。

欢迎关注:

<think>好的,我现在需要帮助用户理解如何在给定的STM32 HAL库代码中设置TIM6定时器,使其产生100Hz的频率。首先,我得仔细看看用户提供的代码,然后分析问题所在。 用户给出的代码是MX_TIM6_Init函数,里面配置了TIM6的预分频器(Prescaler)为83,自动重载值(Period)为65535,并且禁用了自动重装载预加载。用户的问题是,如何调整这些参数使得频率变为100Hz。 首先,我得回忆一下STM32定时器的工作原理。定时器的时钟频率通常是来自系统时钟的分频,比如如果主频是84MHz,那么TIM6的时钟可能也是84MHz。这里假设用户使用的是常见的配置,比如STM32F4系列,默认情况下APB1定时器的时钟是84MHz。 定时器的频率计算涉及到预分频器和自动重载值。定时器的溢出频率计算公式是:溢出频率 = 定时器时钟频率 / (预分频器 + 1) / (自动重载值 + 1)。这里,预分频器的作用是将定时器的时钟分频,然后计数器从0计数到自动重载值,产生一次溢出中断或者更新事件。 用户当前的配置是预分频器83,自动重载值65535。那当前的溢出频率应该是84MHz / (83+1) / (65535+1) ≈ 84,000,000 / 84 / 65536 ≈ 15.258789 Hz。这显然比用户想要的100Hz低很多,所以需要调整这两个参数。 用户的目标是100Hz,也就是每0.01秒产生一次更新事件。所以需要重新计算预分频器和周期值。首先,确定定时器的时钟频率,假设是84MHz。那么总的分频倍数应为84,000,000 / 100 = 840,000。这个总倍数需要分配到预分频器和自动重载值上。 通常,预分频器用于将高频的定时器时钟分频到较低频率,而自动重载值则设置计数器的周期。为了使自动重载值不过大(不超过65535,因为TIM6是16位定时器),需要合理分配分频系数。比如,选择预分频器为839,这样预分频后的时钟频率是84MHz / (839+1) = 84,000,000 / 840 = 100,000 Hz。然后,自动重载值设置为(100,000 Hz / 100 Hz) -1 = 1000 -1 = 999。这样得到的溢出频率就是100Hz。 这样配置的话,预分频器Prescaler设为839,Period设为999。这样计算是否正确?是的,因为(839+1)*(999+1)=840*1000=840,000,刚好是84MHz / 840,000 = 100Hz。 接下来需要检查用户代码中的参数是否允许这样的设置。TIM6是16位定时器,所以Prescaler的最大值是65535,所以839没问题。自动重载值999也远小于65535,所以没问题。 现在回到用户的代码,原来的Prescaler是83,Period是65535。所以应该修改这两个参数。用户需要将Prescaler从83改为839,Period从65535改为999。另外,要注意自动重载预加载是否启用,但用户代码中设置为DISABLE,这可能不影响频率,但会影响何时加载新值。不过,这里主要影响的是频率的参数,所以修改Prescaler和Period即可。 不过,可能用户的主频不是84MHz,这会影响计算。比如,如果是其他频率,比如168MHz,那么预分频器和Period的值也会不同。所以这里需要假设用户的系统时钟,或者指出需要根据实际时钟调整参数。但用户提供的代码中Prescaler是83,通常这可能对应84MHz系统时钟下的分频到1MHz(因为84MHz/(83+1)=1MHz)。比如原来的配置下,定时器的溢出频率是1MHz / 65536 ≈ 15.26Hz。但用户想要的是100Hz,所以需要调整分频和周期。 总结步骤:确定系统时钟频率,计算预分频器和Period的值,使得(Prescaler+1)*(Period+1) = 定时器时钟频率 / 目标频率。然后修改代码中的这两个参数。同时,可能需要检查是否有其他配置影响,比如时钟源是否正确,定时器是否启动等,但用户的问题主要集中在如何设置参数,所以主要调整Prescaler和Period。</think>要设置TIM6定时器的频率为100Hz,需调整预分频器(`Prescaler`)和周期值(`Period`)。以下是具体步骤: --- ### **步骤1:确定定时器时钟源频率** 假设TIM6的时钟源为APB1总线时钟(常见配置)。例如,若主频为84MHz且APB1不分频,TIM6时钟可能为84MHz。需根据实际系统时钟确认此值。 --- ### **步骤2:计算参数公式** 定时器溢出频率公式为: $$ f_{\text{溢出}} = \frac{f_{\text{TIM\_CLK}}}{(\text{Prescaler} + 1) \times (\text{Period} + 1)} $$ 目标频率为100Hz,因此: $$ \frac{f_{\text{TIM\_CLK}}}{(\text{Prescaler} + 1) \times (\text{Period} + 1)} = 100 $$ --- ### **步骤3:分配分频系数** 1. **预分频器(Prescaler)**:将高频时钟分频至中间频率。 2. **周期(Period)**:设置计数器溢出点。 **示例(84MHz时钟下)**: 1. 选择预分频器为839: $$ \text{中间频率} = \frac{84\text{MHz}}{839 + 1} = 100\text{kHz} $$ 2. 计算周期值: $$ \text{Period} + 1 = \frac{100\text{kHz}}{100\text{Hz}} = 1000 \quad \Rightarrow \quad \text{Period} = 999 $$ --- ### **步骤4:修改代码参数** 将原代码中的`Prescaler`和`Period`替换为计算值: ```c htim6.Init.Prescaler = 839; // 原值为83 htim6.Init.Period = 999; // 原值为65535 ``` --- ### **完整配置代码** ```c void MX_TIM6_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; htim6.Instance = TIM6; htim6.Init.Prescaler = 839; // 修改预分频器 htim6.Init.CounterMode = TIM_COUNTERMODE_UP; htim6.Init.Period = 999; // 修改周期值 htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim6) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK) { Error_Handler(); } } ``` --- ### **验证公式** - **计算实际频率**: $$ f_{\text{溢出}} = \frac{84\text{MHz}}{(839 + 1) \times (999 + 1)} = \frac{84,000,000}{840 \times 1000} = 100\text{Hz} $$ --- ### **注意事项** 1. **时钟源确认**:若实际时钟非84MHz(如168MHz),需重新计算参数。 2. **16位限制**:TIM6是16位定时器,`Prescaler`和`Period`均不可超过65535。 3. **启动定时器**:配置后需调用`HAL_TIM_Base_Start()`启动定时器
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值