前置介绍
TIM-输入捕获之编码器模式

什么是输入捕获?
通过定时器记录输入引脚信号到达时刻的功能, 常用于测量外部信号的频率, 周期, 脉宽.
什么是编码器?
用来测量旋转或位移的传感器, 能测量机械部件旋转或直线运动时的位移位置或速度等信息, 并转换成一系列电信号
TIM 高级定时器框图-输入捕获

阶段 1:外部输入通道(橙色区域)
左侧的 TIMx_CH1~TIMx_CH4 是定时器的 4 个输入通道,负责接收外部信号。
- 信号可通过 直连 (直接进入通道)或 异或逻辑(通过 XOR 选择性组合通道信号,比如多路信号逻辑运算后输入)进入后续处理。
阶段 2:输入滤波与边沿检测(紫色区域)
每个通道的信号进入 "输入滤波器 + 边沿检测器" 模块:
- 滤波器:过滤信号中的高频噪声,避免误触发;
- 边沿检测器 :检测信号的 上升沿/下降沿 (比如脉冲的起始/结束),并将边沿事件转化为触发信号(
TI1FP1/2、TI2FP1/2等)。
阶段 3:通道选择与预分频(粉色+灰色区域)
通过 通道选择逻辑 (如 TIxFPx、TRC 等),选择要捕获的信号源(比如从多个通道中选 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 秒" 和当前位置.