野火STM32_HAL库版课程笔记-TIM通道捕获应用之编码器模式

前置介绍

TIM-输入捕获之编码器模式
什么是输入捕获?

通过定时器记录输入引脚信号到达时刻的功能, 常用于测量外部信号的频率, 周期, 脉宽.

什么是编码器?

用来测量旋转或位移的传感器, 能测量机械部件旋转或直线运动时的位移位置或速度等信息, 并转换成一系列电信号

TIM 高级定时器框图-输入捕获
阶段 1:外部输入通道(橙色区域)

左侧的 TIMx_CH1~TIMx_CH4 是定时器的 4 个输入通道,负责接收外部信号。

  • 信号可通过 直连 (直接进入通道)或 异或逻辑(通过 XOR 选择性组合通道信号,比如多路信号逻辑运算后输入)进入后续处理。
阶段 2:输入滤波与边沿检测(紫色区域)

每个通道的信号进入 "输入滤波器 + 边沿检测器" 模块:

  • 滤波器:过滤信号中的高频噪声,避免误触发;
  • 边沿检测器 :检测信号的 上升沿/下降沿 (比如脉冲的起始/结束),并将边沿事件转化为触发信号(TI1FP1/2TI2FP1/2 等)。
阶段 3:通道选择与预分频(粉色+灰色区域)

通过 通道选择逻辑 (如 TIxFPxTRC 等),选择要捕获的信号源(比如从多个通道中选 1 路,或组合多路信号)。

  • 选中的信号进入 预分频器(灰色模块):对信号进行分频(比如 1/2 分频、1/4 分频等),调整捕获频率。
阶段 4:捕获/比较寄存器(蓝色区域)

预分频后的信号(ICxPS)触发 "捕获/比较寄存器"CC1~CC4):

  • 当边沿事件发生时,计数器(CNT)的当前值会被"捕获"并存入对应通道的寄存器 (比如通道 1 捕获到边沿,CNT 值存入 捕获/比较 1 寄存器)。
  • 这些寄存器可读取,用于计算 脉冲宽度、频率、占空比 等(比如两次捕获的时间差 = 脉冲周期)。
阶段 5:计数与基准(黄色区域)

右侧黄色模块是定时器的 核心计数单元

  • PSC(预分频器):对定时器时钟 CK_PSC 分频,得到计数时钟 CK_CNT
  • CNT(计数器):在 CK_CNT 驱动下递增/递减/停止,提供"时间基准";
  • 自动重装载寄存器:决定计数器的溢出周期(比如计数到 1000 后自动归零,形成固定周期)。
EC11 编码器模块

在 STM32 中自带编码器模式, 其就可以采集例如 A 相的下降沿, 就会自动判断 B 相此时如果是高电平, 那么就能判断出其此时是顺时针, (判断哪一项在前, 哪一项在后, 得出 顺时针 / 逆时针 旋转)

STM32F10x - 中文参考手册
TIM-编码器模式所用函数

HAL_TIM_Encoder_Start()

__HAL_TIM_GET_COUNTER()

__HAL_TIM_IS_TIM_COUNTING_DOWN()

项目配置

TIM3

Combined Channels : Encoder Mode

Prescaler : 4 - 1

Encoder Mode : Encoder Mode TI1 and TI2

注意: 在编码器模式下,极性设置 (Encoder - Polarity) 不是用来选择只检测上升沿,而是用来定义信号的反相(Inversion)。如果设置为 Rising,表示信号不反相;如果设置为 Falling,表示信号经过反相器。硬件依然会自动检测双边沿。

GPIO

选择 PA8 为 GPIO_Input

设置为 Pull-up (上拉模式), User Label 为 EC11_SW

代码部分

记得勾选 Use MicroLIB

复制代码
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>  // 使用 printf 函数
#include <string.h> // 使用 strncmp

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
int16_t encoder_cnt = 0;      // 编码器当前计数值 (用于获取旋转变化)
int16_t encoder_last = 0;     // 上一次编码器计数值 (用于比较判断旋转方向和增量)
uint32_t no_move_time = 0;    // 编码器未移动的时间 (用于检测是否静止)

uint8_t encoder_static = 0;   // 0: 运动, 1: 静止

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
    encoder_cnt = __HAL_TIM_GET_COUNTER(&htim3);
    
    // 检测是否移动
    if (encoder_cnt != encoder_last)
    {
      
      encoder_static = 0;  // 有动作, 等待静止
      no_move_time = HAL_GetTick(); // 记录最后动作事件
      
      int16_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
    {
      // 超过两秒静止
      if (HAL_GetTick() - no_move_time > 2000 && encoder_static == 0)
      {
        encoder_static = 1;
        printf("当前编码器静止超过2秒, 当前位置: %d \r\n", encoder_cnt);
      }
    }
    
    // 检测 SW 按钮是否被按下
    if (HAL_GPIO_ReadPin(GPIOA, EC11_SW_Pin) == GPIO_PIN_RESET)
    {
      HAL_Delay(20); // 消抖
      if (HAL_GPIO_ReadPin(GPIOA, EC11_SW_Pin) == GPIO_PIN_RESET)
      {
        printf("SW按钮被按下 \r\n");
        while(HAL_GPIO_ReadPin(GPIOA, EC11_SW_Pin) == GPIO_PIN_RESET);
      }
    }
    
    __HAL_TIM_SET_COUNTER(&htim3, 0);
    
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

/* USER CODE BEGIN 4 */

/**
  * @brief  重定向 printf 的输出到串口
  * @param  ch: 要发送的字符
  * @param  f:  文件指针 (标准库要求的参数, 一般不使用)
  * @retval 返回发送的字符
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

/* USER CODE END 4 */

程序现象

分别顺时针 / 逆时针 旋转编码器, 输出移动量与当前位置.

按下按钮, 触发 "SW 按钮已被按下"

两秒时间没有旋转编码器, 输出 "当前编码器已静止超过 2 秒" 和当前位置.

相关推荐
柔情的菜刀2 小时前
踩坑实录|RK3588 BT1120 输出调试全解(适配GS2972)
嵌入式硬件
老虎06272 小时前
LeetCode热题100 刷题笔记(第四天)二分 「 寻找两个正序数组的中位数」
笔记·算法·leetcode
Lugas Luo2 小时前
Ascend 310B 定制 SDHCI 主机控制器源码深层次劫持与优化解析
linux·嵌入式硬件
llm大模型算法工程师weng2 小时前
在flomo中安放“不确定”:一款笔记产品如何让人“被看见”
笔记
今儿敲了吗2 小时前
51| 八皇后
c++·笔记·学习·算法·深度优先
适应规律3 小时前
强化学习笔记(赵世钰)
笔记·线性代数·概率论
恒森宇电子有限公司4 小时前
芯晞微CSM057 线性充电管理芯片 封装SOT23-6
单片机·嵌入式硬件
鸽子一号4 小时前
c#笔记之泛型和结构体、枚举
笔记
蓝凌y5 小时前
51单片机之LCD1602
单片机·嵌入式硬件·51单片机