一、核心功能模块

输入捕获:测量外部脉冲的宽度或频率,需配合跳变沿捕获和溢出计数。
输出比较:产生特定宽度脉冲,或用于驱动DAC。
PWM 生成:输出脉宽调制信号,频率由ARR寄存器决定,占空比由CCRx寄存器决定。
单脉冲模式:接收一个触发信号后,输出单个脉冲。
可编程死区互补输出:单通道输出两路互补PWM,切换时插入死区时间,防止短路。
重复计数器(RCR):设定N+1次计数器溢出/下溢后,才产生真正的更新事件(UEV)。

二、时钟来源配置

定时器的时钟源决定了计数器(CNT)的计数基准。

1. 内部时钟

来源:芯片内部系统时钟(如APB总线时钟)。
配置:TIMx_SMCR 寄存器 SMS [2:0] 设为 000(禁止从模式),TIMx_CR1 寄存器 CEN 位置 1(使能计数器)。
路径:

CK_INT→预分频器(PSC)→计数器(CNT)

2. 外部时钟模式

外部时钟模式 1:通过 TIMx_CH1/2/3/4 引脚输入外部信号,经输入滤波器、边沿检测器处理后作为时钟。
信号路径1:

TIMx_CHx引脚→输入滤波器→边沿检测器→TIxFPx→触发选择器→TRGI→从模式控制器→CK_PSC→预分频器→计数器。

外部时钟模式2:外部信号直接驱动计数器,不经过触发控制器。

3. 内部触发输入(ITRx)- 定时器级联

原理:将一个定时器的触发输出(TRGO)连接到另一个定时器的内部触发输入(ITRx)。
信号路径:

TIMx计数器溢出→更新事件→TRGO输出→连接到目标定时器ITRx→TRGI→从模式控制器→CK_PSC→预分频器→计数器

应用:实现定时器的同步或级联,扩展定时周期或创建复杂时序。

三、PWM配置

1. 生成原理

频率:由自动重装载寄存器(ARR)决定。

频率 = 定时器时钟 / (ARR + 1)

占空比:由捕获/比较寄存器(CCRx)决定。

占空比 = CCRx / (ARR + 1)

2. 工作模式

PWM 模式 1(向上计数):

CNT<CCRx 时,OCxREF 为有效电平;
CNT≥CCRx 时,为无效电平。

PWM 模式 2(向上计数):

CNT<CCRx 时,OCxREF 为无效电平;
CNT≥CCRx 时,为有效电平。

极性控制:通过 CCER 寄存器 CCxP 位,定义有效电平为高或低。

3. 计数与中断特性(中央对齐模式)

CMS=01(中央对齐模式 1):

仅计数器向下计数且 CNT=CCRx 时置位比较标志,每个 PWM 周期 1 次比较中断。

CMS=10(中央对齐模式 2):

仅计数器向上计数且 CNT=CCRx 时置位比较标志,每个 PWM 周期 1 次比较中断。

CMS=11(中央对齐模式 3):

计数器向上 / 向下计数且 CNT=CCRx 时均置位比较标志,每个 PWM 周期 2 次比较中断。

4. 输出控制与预装载机制

输出控制

输出信号的状态由多个寄存器位共同决定。

MOE(主输出使能)
OSSI(空闲状态输出)
OSSR(运行状态输出)
CCxE/CCxNE(通道及互补通道输出使能)

预装载机制

作用:确保PWM参数(ARR, CCRx)在更新事件时同步更新,避免修改参数导致波形出现毛刺或断裂。

ARPE位:使能ARR寄存器的预装载。
OCxPE位:使能CCRx寄存器的预装载。

四、关键保护与高级功能

1. 死区时间配置

作用:防止半桥 / 全桥电路中两个开关管同时导通导致短路。
原理:在互补信号(OCx和OCxN)的切换时刻,插入一段双方都为无效电平的"死区时间"。
实现:通过死区时间发生器,基于 OCxREF 参考波形生成 OCx 和 OCxN 两路互补信号。
配置:由 BDTR 寄存器 DTG [7:0] 位设定。

T_dts=T_clk*T_dtg
T_clk 为定时器时钟周期
T_dtg 为 DTG 配置值

2. 刹车(断路)功能

触发条件

内部断路:时钟安全系统(CSS)检测到时钟故障。
外部断路:BKIN 引脚(最高优先级硬件保护引脚)输入有效信号。

断路后动作

主输出使能(MOE)位清零,输出进入无效状态。
输出通道及互补通道电平设为安全状态(由 OSSI 和 OSSR 位控制)。
置位断路中断标志(BIF),可触发中断和 DMA 请求。
启用自动输出使能(AOE)时,MOE 位在下一个更新事件自动置 1,恢复输出。

3. 重复计数器(RCR)

功能:设定在 N+1 个计数器上溢/下溢事件后,才产生一次真正的更新事件(UEV)。
应用:方便地生成频率为基准PWM 1/(N+1) 的复杂波形,无需软件频繁干预。

五、输入测量功能

1. 输入捕获(测量脉宽)

原理:通过两次跳变沿捕获计数器值,差值乘以计数周期即为脉宽时间。

脉宽 = (第二次捕获值 - 第一次捕获值 + 溢出次数 * ARR) * 计数周期

步骤:

配置输入捕获通道和初始捕获边沿(如上升沿);
第一次捕获后记录值并切换边沿(如下降沿);
第二次捕获后计算两次值的差值;
脉宽超 ARR 时,需通过溢出中断计数溢出次数。

2. PWM输入捕获模式(特殊模式)

使用 CH1 和 CH2 两个捕获通道,仅支持 TIMx 的 CH1/CH2 通道。

配置:

使用两个捕获通道(通常为IC1和IC2);
IC1捕获上升沿,并配置为触发信号(TRC);
IC2捕获下降沿;
设置从模式为复位模式,当IC1捕获到上升沿时,计数器立即复位。

结果:

周期 = IC1捕获的值(因为每次上升沿计数器都复位)。
高电平时间 = IC2捕获的值。
占空比 = (IC2 / IC1) * 100%

六、编码器模式

功能:测量机械旋转/直线运动的位置、速度,将机械信号转换为电信号,本质是带方向选择的外部时钟应用。

工作前提:计数器仅在0~TIMx_ARR之间计数,开始前需配置TIMx_ARR寄存器;捕获器、比较器等其他特性正常工作。

计数方式与规则:

仅在 TI1 边沿计数:TI1 边沿触发计数,TI2 电平决定方向
TI2 低→TI1 上升增 / 下降减;TI2 高→TI1 上升减 / 下降增

仅在 TI2 边沿计数:TI2 边沿触发计数,TI1 电平决定方向
TI1 低→TI2 上升增 / 下降减;TI1 高→TI2 上升减 / 下降增

在 TI1 和 TI2 边沿计数:两通道所有边沿均触发计数,分辨率提升 4 倍;
TI1 领先时计数器递增,TI2 领先时递减(适用于正交编码器)。

七、相关代码及分析

1. 定时器初始化函数

void MX_TIM1_Init(void)
{
  // 定义并初始化定时器时钟配置结构体
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  // 定义并初始化定时器主模式配置结构体
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */
  /* USER CODE END TIM1_Init 1 */
  // 用户代码区域1,用户可以在此添加自定义初始化代码

  // 设置定时器实例为TIM1(高级定时器)
  htim1.Instance = TIM1;
  // 设置预分频器为7200-1,即7199
  // 如果系统时钟为72MHz,则定时器时钟 = 72MHz / (7199+1) = 10kHz
  htim1.Init.Prescaler = 7200-1;
  // 设置计数器模式为向上计数
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  // 设置自动重载寄存器周期为1000-1,即999
  // 计数器从0计数到999,共1000个计数,产生更新事件
  htim1.Init.Period = 1000-1;
  // 设置时钟分频为不分频
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  // 设置重复计数器为0(高级定时器特有功能)
  htim1.Init.RepetitionCounter = 0;
  // 使能自动重载预装载功能
  // 新的ARR值会在下次更新事件时生效,避免在运行时修改ARR导致的不一致
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  
  // 初始化定时器基础配置,如果初始化失败则调用错误处理函数
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 配置时钟源为内部时钟
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  // 配置定时器时钟源,如果配置失败则调用错误处理函数
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 设置主模式触发输出为复位(不产生触发信号)
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  // 禁用主从模式
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  // 配置主模式同步设置,如果配置失败则调用错误处理函数
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}

2. 定时器中断启动函数

HAL_TIM_Base_Start_IT(&htim1);

3. 定时器周期溢出回调函数

// 当定时器计数器达到重装载值(周期值)时,HAL库会自动调用此函数
void HAL_TIM_PeriodElapseCallback(TIM_HandleTypeDef *htim)
{
	// 检查触发回调的定时器实例是否是TIM2
	if(htim->Instance==TIM2)
	{
		// 切换GPIOA端口上LED_R_Pin、LED_G_Pin、LED_B_Pin引脚的电平状态
		// 如果引脚当前为高电平,则变为低电平;如果为低电平,则变为高电平
		HAL_GPIO_TogglePin(GPIOA,LED_R_Pin|LED_G_Pin|LED_B_Pin);
	}
}

4. 使用TIM实现LED闪烁频率切换

// 定义定时器1的自动重装载值数组,包含两个不同的周期值
// 1000-1 = 999,对应较短的定时周期
// 10000-1 = 9999,对应较长的定时周期
uint32_t tim1_arr[2] = {1000-1, 10000-1};

// 定义模式变量并初始化为0,用于在两种定时周期间切换
uint8_t mode = 0;

// 声明系统时钟配置函数
void SystemClock_Config(void);

int main(void)
{
  // 复位所有外设,初始化Flash接口和系统滴答定时器
  HAL_Init();

  // 配置系统时钟
  SystemClock_Config();

  // 初始化所有配置的外设
  MX_GPIO_Init();    // 初始化GPIO
  MX_TIM1_Init();    // 初始化定时器1

  // 启动定时器1的中断模式
  HAL_TIM_Base_Start_IT(&htim1);

  while (1)
  {
    // 检测KEY_1按键是否被按下(高电平)
    if(HAL_GPIO_ReadPin(GPIOA,KEY_1_Pin) == GPIO_PIN_SET)
    {
      // 切换模式(0变1,1变0)
      mode = !mode;
      
      // 停止定时器1的中断
      HAL_TIM_Base_Stop_IT(&htim1);
      
      // 根据当前模式设置定时器1的自动重装载值
      // mode=0时使用tim1_arr[0]=999,mode=1时使用tim1_arr[1]=9999
      __HAL_TIM_SET_AUTORELOAD(&htim1, tim1_arr[mode]);
      
      // 将定时器1的计数器清零
      __HAL_TIM_SET_COUNTER(&htim1, 0);
      
      // 重新启动定时器1的中断
      HAL_TIM_Base_Start_IT(&htim1);
      
      // 按键消抖:等待按键释放
      while(HAL_GPIO_ReadPin(GPIOA,KEY_1_Pin) == GPIO_PIN_SET);
    }
  }
}

5. 使用PWM实现舵机控制

// 定义SG90舵机PWM脉宽范围(单位:微秒)
#define SG90_MIN 500   // 舵机-90°位置对应的脉宽(0.5ms)
#define SG90_MAX 2500  // 舵机+90°位置对应的脉宽(2.5ms)
#define SG90_STEP 500  // 舵机每次转动的步进值(约45°)

// 定义当前舵机角度对应的PWM脉宽值,初始化为500(-90°位置)
uint16_t sg90_angle = 500;

// 声明系统时钟配置函数
void SystemClock_Config(void);

int main(void)
{
  // 复位所有外设
  HAL_Init();

  // 配置系统时钟 
  SystemClock_Config();

  // 初始化所有配置的外设 
  MX_GPIO_Init();    // 初始化GPIO
  MX_TIM3_Init();    // 初始化定时器3(用于PWM输出)

  // 启动TIM3通道3的PWM输出(对应引脚PB0)
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
  
  // 初始化舵机角度到-90°位置
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, sg90_angle);

  while (1)
  {
    // 检测KEY1按键是否按下(增加角度)
    if(HAL_GPIO_ReadPin(GPIOA, KEY1_Pin) == GPIO_PIN_SET)
    {
      // 检查是否超过最大角度限制
      if(sg90_angle + SG90_STEP <= SG90_MAX)
      {
        // 增加舵机角度(增加PWM脉宽)
        sg90_angle += SG90_STEP;
      }		
      
      // 更新PWM比较值,改变舵机角度
      __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, sg90_angle);
      
      // 延时300ms,确保舵机有足够时间转动到位
      HAL_Delay(300);
      
      // 等待按键释放(消抖)
      while(HAL_GPIO_ReadPin(GPIOA, KEY1_Pin) == GPIO_PIN_SET);
    }

    // 检测KEY2按键是否按下(减少角度)
    // 注意:这里有错误,应该检测GPIOC,KEY2_Pin,但等待释放时检测的是KEY1
    if(HAL_GPIO_ReadPin(GPIOC, KEY2_Pin) == GPIO_PIN_SET)
    {
      // 检查是否超过最小角度限制
      if(sg90_angle - SG90_STEP >= SG90_MIN)
      {
        // 减少舵机角度(减少PWM脉宽)
        sg90_angle -= SG90_STEP;
      }		
      
      // 更新PWM比较值,改变舵机角度
      __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, sg90_angle);
      
      // 延时300ms,确保舵机有足够时间转动到位
      HAL_Delay(300);
      
      // 等待按键释放(消抖)- 这里有错误,应该等待KEY2释放而不是KEY1
      while(HAL_GPIO_ReadPin(GPIOA, KEY1_Pin) == GPIO_PIN_SET);
    }  
  }
}

6. 使用PWM实现呼吸灯

// 定义PWM占空比的值,对应CCR寄存器的值,初始为0(最低亮度)
uint16_t pwm_val = 0;

// 定义亮度变化方向:10表示变亮,-10表示变暗,初始为变亮方向
int8_t dir = 10;

// 声明系统时钟配置函数
void SystemClock_Config(void);

int main(void)
{
  // 复位所有外设,初始化Flash接口和系统滴答定时器 
  HAL_Init();

  // 配置系统时钟 
  SystemClock_Config();

  // 初始化所有配置的外设 
  MX_GPIO_Init();    // 初始化GPIO
  MX_TIM2_Init();    // 初始化定时器2(用于PWM输出)

  // 启动TIM2的三个PWM通道
  // 通道2、3、4分别控制不同的LED(可能是RGB三色LED)
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);

  while (1)
  {
    // 设置三个PWM通道的比较值(占空比),控制LED亮度
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, pwm_val);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, pwm_val);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, pwm_val);

    // 根据方向更新PWM值
    pwm_val += dir;

    // 边界检查:当PWM值达到最大值1000时,改变方向为减小(变暗)
    if(pwm_val >= 1000) 
        dir = -10;

    // 边界检查:当PWM值达到最小值0时,改变方向为增加(变亮)
    if(pwm_val <= 0) 
        dir = 10;

    // 延时5ms,控制呼吸灯变化速度
    HAL_Delay(5);
  }
}

7. 使用TIM触发ADC采集

// 定义变量存储ADC原始值
uint32_t adc_val = 0;

// 定义变量存储计算出的电压值
float voltage = 0.0f;

// 定义变量存储计算出的温度值
float tem = 0.0f;

// 声明系统时钟配置函数
void SystemClock_Config(void);

int main(void)
{
  // 复位所有外设,初始化Flash接口和系统滴答定时器 
  HAL_Init();

  // 配置系统时钟 
  SystemClock_Config();

  // 初始化所有配置的外设 
  MX_GPIO_Init();           // 初始化GPIO
  MX_ADC1_Init();           // 初始化ADC1(用于读取内部温度传感器)
  MX_TIM3_Init();           // 初始化定时器3
  MX_USART1_UART_Init();    // 初始化USART1串口(用于输出温度数据)

  // 开始ADC1的校准,提高ADC精度
  HAL_ADCEx_Calibration_Start(&hadc1);
  
  // 启动ADC1转换
  HAL_ADC_Start(&hadc1);
  
  // 启动定时器3(可能用于其他功能或定时触发ADC)
  HAL_TIM_Base_Start(&htim3);
  
  // 延时3秒,等待系统稳定
  HAL_Delay(3000);

  while (1)
  {
    /* USER CODE END WHILE */

    // 等待ADC转换完成,使用最大超时时间
    if(HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY) == HAL_OK)
    {
      // 获取ADC转换结果(12位分辨率,范围0-4095)
      adc_val = HAL_ADC_GetValue(&hadc1);
      
      // 将ADC原始值转换为电压值(假设参考电压为3.3V)
      // 公式:电压 = (ADC值 / 最大ADC值) × 参考电压
      voltage = (float)adc_val / 4095 * 3.3f;
      
      // 根据STM32内部温度传感器特性计算温度
      // 公式:温度 = (1.43 - 电压) / 0.0043 + 25
      // 其中:1.43V是25°C时的典型电压,0.0043是温度系数(V/°C)
      tem = (1.43f - voltage) / 0.0043f + 25;
      
      // 通过串口输出温度值,保留3位小数
      printf("当前芯片内部温度为:%.3f\r\n", tem);
    }
  }
}

8. TIM编码器模式

// 定义编码器当前计数值
int16_t encoder_cnt = 0;

// 定义编码器上一次的计数值,用于检测变化
int16_t encoder_last = 0;

// 记录编码器停止移动的时间点
uint32_t no_move_time = 0;

// 编码器静止状态标志:0-运动中,1-已静止
uint8_t encoder_static = 0;

// 声明系统时钟配置函数
void SystemClock_Config(void);

int main(void)
{
  // 复位所有外设,初始化Flash接口和系统滴答定时器 
  HAL_Init();

  // 配置系统时钟 
  SystemClock_Config();

  // 初始化所有配置的外设
  MX_GPIO_Init();           // 初始化GPIO
  MX_TIM3_Init();           // 初始化定时器3(配置为编码器模式)
  MX_USART1_UART_Init();    // 初始化USART1串口(用于输出状态信息)

  // 启动定时器3的编码器接口模式,同时使能两个通道
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

  while (1)
  {
    // 获取编码器当前的计数值
    encoder_cnt = __HAL_TIM_GET_COUNTER(&htim3);
    
    // 检查编码器计数值是否发生变化(即编码器是否转动)
    if(encoder_cnt != encoder_last)
    {
      // 重置静止标志,表示编码器正在运动
      encoder_static = 0;
      
      // 记录当前时间作为最后一次运动的时间
      no_move_time = HAL_GetTick();
      
      // 计算计数值的变化量
      uint16_t diff = encoder_cnt - encoder_last;
      
      // 判断编码器转动方向
      if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim3))
      {
        // 如果定时器在递减计数,表示逆时针转动
        printf("逆时针运行:%d, 当前位置: %d\r\n", diff, encoder_cnt);
      }
      else
      {
        // 如果定时器在递增计数,表示顺时针转动
        printf("顺时针运行:%d, 当前位置: %d\r\n", diff, encoder_cnt);
      }
      
      // 更新上一次的计数值
      encoder_last = encoder_cnt;
    }
    else
    {
      // 编码器没有移动,检查是否静止超过2秒且尚未报告静止状态
      if(HAL_GetTick() - no_move_time > 2000 && encoder_static == 0)
      {
        // 设置静止标志,避免重复报告
        encoder_static = 1;
        
        // 报告编码器已静止超过2秒
        printf("当前编码器静止超过2s,当前位置: %d\r\n", encoder_cnt);
      }
    }
    
    // 检测编码器的按键(SW)是否被按下(低电平有效)
    if(HAL_GPIO_ReadPin(GPIOA, EC1_SW_Pin) == GPIO_PIN_RESET)
    {
      // 延时20ms进行按键消抖
      HAL_Delay(20);
      
      // 再次检测按键状态,确认按键确实被按下
      if(HAL_GPIO_ReadPin(GPIOA, EC1_SW_Pin) == GPIO_PIN_RESET)
      {
        // 报告按键被按下
        printf("sw按键被按下\r\n");
        
        // 等待按键释放(防止连续触发)
        while(HAL_GPIO_ReadPin(GPIOA, EC1_SW_Pin) == GPIO_PIN_RESET);
      }
    }
  }
}

 

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐