一、方案 A(推荐):编码器模式吃脉冲(TI1 = STEP,TI2 = DIR)
核心思路
把定时器设为 Encoder TI1 模式:每个 STEP 上升沿计一次,在那个沿的瞬间用 TI2(DIR)的电平决定加/减。硬件完成“锁存方向”的动作,不用担心一个 FOC 周期内 DIR 翻转多次造成错账。
典型接线(以 TIM2 为例)
- STEP →
TIM2_CH1
(PA0,AF1) - DIR →
TIM2_CH2
(PA1,AF1)
-(可选)给 STEP、DIR 做差分/光耦/RC 小滤波;靠近 MCU 端各串 ~100 Ω + 100 pF 抑毛刺。
HAL 初始化代码(TIM2和TIM5)
// ========== GPIO: PA0/PA1 复用为 TIM2_CH1/CH2 ==========
static void StepDir_GPIO_Init_TIM2(void) {__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef G = {0};G.Mode = GPIO_MODE_AF_PP;G.Pull = GPIO_PULLDOWN; // 依现场电路定:上拉或下拉二选一,空闲低更稳G.Speed = GPIO_SPEED_FREQ_VERY_HIGH;G.Alternate = GPIO_AF1_TIM2;// STEP -> PA0 (TIM2_CH1)G.Pin = GPIO_PIN_0;HAL_GPIO_Init(GPIOA, &G);// DIR -> PA1 (TIM2_CH2)G.Pin = GPIO_PIN_1;HAL_GPIO_Init(GPIOA, &G);
}// ========== TIM2: 编码器模式(TI1计步,TI2判方向) ==========
void StepDir_Encoder_Init_TIM2(void) {__HAL_RCC_TIM2_CLK_ENABLE();StepDir_GPIO_Init_TIM2();htim2.Instance = TIM2;htim2.Init.Prescaler = 0; // 1:1htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 编码器模式下无实际影响htim2.Init.Period = 0xFFFFFFFF; // 32位满量程htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.RepetitionCounter = 0;TIM_Encoder_InitTypeDef enc = {0};enc.EncoderMode = TIM_ENCODERMODE_TI1; // 用TI1为时钟,TI2提供方向// TI1 = STEPenc.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 计上升沿(与上位保持一致)enc.IC1Selection = TIM_ICSELECTION_DIRECTTI;enc.IC1Prescaler = TIM_ICPSC_DIV1;enc.IC1Filter = 8; // 数字滤波:8~12 先试;若高频步进需减小该值// TI2 = DIR(作为方向电平被锁存)enc.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 极性对电平锁存无本质影响enc.IC2Selection = TIM_ICSELECTION_DIRECTTI;enc.IC2Prescaler = TIM_ICPSC_DIV1;enc.IC2Filter = 8;HAL_TIM_Encoder_Init(&htim2, &enc);TIM_MasterConfigTypeDef master = {0};master.MasterOutputTrigger = TIM_TRGO_RESET;master.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim2, &master);__HAL_TIM_SET_COUNTER(&htim2, 0);HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
}
在 FOC 周期读取步数增量
// 每个 FOC 控制中断(例如 10 kHz)调用一次
static uint32_t s_last_cnt = 0;int32_t StepDir_ReadDelta_EncoderMode(void) {uint32_t now = __HAL_TIM_GET_COUNTER(&htim2);// 利用无符号溢出特性做差,再转有符号即为带方向的增量int32_t d = (int32_t)(now - s_last_cnt);s_last_cnt = now;return d; // 单位:步
}// 例:把步→机械角度(或直线位移)
/*
const float THETA_PER_STEP = 2.0f * (float)M_PI / (steps_per_rev * microstep * gear_ratio);
pos_ref += StepDir_ReadDelta_EncoderMode() * THETA_PER_STEP;
*/
要点
- DIR 建立时间必须由上游(A 板)满足:变向后延时 ≥2–5 µs 再发第一步。
- STEP 极性不对时改
IC1Polarity
或对端极性。 - 该方案不需要 EXTI,不会因 DIR 多次翻转出错,最省心。
二、方案 B:外部时钟模式(STEP)+ DIR GPIO(带 EXTI 结清)
核心思路
- STEP 接到 定时器外部时钟(ETR 或 TI1),只向上计数;不进中断。
- DIR 接到 GPIO,并配置 EXTI 上/下沿触发。每次 DIR 翻转,立即把当前计数器的步数“按旧方向结清并清零”,随后更新方向标志。
- 在 FOC 周期里再把“从上次事件到现在”的剩余步数按“当前方向”结清,得到本周期净步数。
这就等价于“硬件按步沿锁存方向”,避免“一个周期多次翻转”的错账。
典型接线(以 TIM2 外部时钟2为例)
- STEP →
TIM2_ETR
(PA15,AF1) (或选择 TI1 外部时钟1:PA0) - DIR → 任意 GPIO(例:PB3)
若你更倾向 TI1 模式:把 STEP 接
TIM2_CH1
,将 TIM2 配置为 External Clock Mode 1(SMCR.TS=TI1FP1 + SMS=ECM1)。其余逻辑一致。
HAL 初始化代码(ETR 版本)
// ----- 全局状态 -----
TIM_HandleTypeDef htim2;
volatile int g_dir_sign = +1; // 当前方向(+1/-1)
volatile int32_t g_step_accum = 0; // 已结清的步(带符号)// ====== 1) GPIO ======
static void StepDir_GPIO_Init_ExternalClock(void) {__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();// STEP -> PA15 (TIM2_ETR, AF1)GPIO_InitTypeDef G = {0};G.Pin = GPIO_PIN_15;G.Mode = GPIO_MODE_AF_PP;G.Pull = GPIO_PULLUP;G.Speed = GPIO_SPEED_FREQ_VERY_HIGH;G.Alternate = GPIO_AF1_TIM2;HAL_GPIO_Init(GPIOA, &G);// DIR -> PB3 (普通输入 + EXTI)G.Pin = GPIO_PIN_3;G.Mode = GPIO_MODE_IT_RISING_FALLING; // 上/下沿都触发G.Pull = GPIO_PULLUP; // 依现场决定HAL_GPIO_Init(GPIOB, &G);// NVIC for EXTI3 (PB3 对应 EXTI3)HAL_NVIC_SetPriority(EXTI3_IRQn, 1, 0);HAL_NVIC_EnableIRQ(EXTI3_IRQn);
}// ====== 2) TIM2 外部时钟(ETR) ======
void StepDir_Init_ExternalClock_ETR(void) {__HAL_RCC_TIM2_CLK_ENABLE();StepDir_GPIO_Init_ExternalClock();// 初始化当前方向(按现有电平)g_dir_sign = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) ? +1 : -1;htim2.Instance = TIM2;htim2.Init.Prescaler = 0;htim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 0xFFFFFFFF; // 32位htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;HAL_TIM_Base_Init(&htim2);TIM_ClockConfigTypeDef clk = {0};clk.ClockSource = TIM_CLOCKSOURCE_ETRMODE2; // ETR 外部时钟模式2HAL_TIM_ConfigClockSource(&htim2, &clk);// 如需设置 ETR 滤波/极性/预分频,可用 LL 接口更细化(见下)__HAL_TIM_SET_COUNTER(&htim2, 0);HAL_TIM_Base_Start(&htim2);
}// (可选)用 LL 细化 ETR 滤波/极性(HAL 有的库不暴露这些)
/*
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
// ETR滤波 ETF,ETP极性,ETPS预分频
LL_TIM_ConfigETR(TIM2, LL_TIM_ETR_POLARITY_NONINVERTED,LL_TIM_ETR_PRESCALER_DIV1,LL_TIM_ETR_FILTER_FDIV32_N8); // 例:较强滤波
LL_TIM_EnableExternalClock(TIM2); // ECM2
*/// ====== 3) DIR EXTI 中断:翻转即结清 ======
void EXTI3_IRQHandler(void) {if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_3) != RESET) {__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3);// 读并清 STEP 计数器(这一段属于“旧方向”)uint32_t steps = __HAL_TIM_GET_COUNTER(&htim2);__HAL_TIM_SET_COUNTER(&htim2, 0);g_step_accum += (int32_t)steps * g_dir_sign;// 更新为新方向(当前电平)g_dir_sign = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) ? +1 : -1;}
}
FOC 周期里合并“剩余步数”并清零
注意与 EXTI 的并发:用关键段确保读-清-累加的原子性,避免 EXTI 在中途打断造成重复/漏记。
int32_t StepDir_ReadDelta_ExternalClock(void) {uint32_t primask = __get_PRIMASK(); // 备份中断状态__disable_irq(); // 进关键段(短)uint32_t steps = __HAL_TIM_GET_COUNTER(&htim2);__HAL_TIM_SET_COUNTER(&htim2, 0);int32_t total = g_step_accum + (int32_t)steps * g_dir_sign;g_step_accum = 0;if (!primask) __enable_irq();return total; // 本周期净步(带符号)
}
如果改用 TI1 外部时钟模式(非 ETR)
把 STEP 接 TIM2_CH1
(PA0),改成 External Clock Mode 1。示意 LL 配置(HAL 有版本差异):
// 关键点:SMCR.SMS = External Clock Mode 1,SMCR.TS = TI1FP1
LL_TIM_SetTriggerInput(TIM2, LL_TIM_TS_TI1FP1);
LL_TIM_SetSlaveMode(TIM2, LL_TIM_SLAVEMODE_EXTERNAL1);
// CH1 输入滤波/极性
LL_TIM_IC_SetFilter(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV32_N8);
LL_TIM_IC_SetPolarity(TIM2, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
LL_TIM_EnableCounter(TIM2);
DIR 部分与上文 EXTI 逻辑相同。
要点
- A 板必须满足
t_dir_setup
:变向→延时≥2–5 µs→再发第一步; - DIR EXTI 可做软件去抖:若检测到相邻中断时间 < 1–2 µs,可判定毛刺丢弃;
- STEP 最高频受限于信号质量/滤波设置。数字滤波数值越大,抗干扰越强但最小脉宽要求也更高;可从 8~12 试起,再按需要减小。
工程级细节与建议(两方案通用)
-
计数位宽:用 TIM2/TIM5(32 位) 把溢出顾虑降到最低;若被占用,用 16 位计数器也行,但要在 FOC 周期里读增量避免溢出。
-
单位换算:
// 步 -> 机械角度rad theta_per_step = 2π / (motor_steps_per_rev * microstep * gear_ratio); pos_ref += delta_steps * theta_per_step;// 或换成速度(rad/s) vel_ref = (delta_steps * theta_per_step) * FOC_rate; // FOC_rate = 控制周期频率
-
极性/方向:若方向与期望相反,优先改上游 DIR 极性;或在本地把
g_dir_sign
取反/交换 TI 极性。 -
毛刺与布线:长线优先差分、近端 RC、上/下拉一致;数字滤波尽量启用(ICxF/ETF)。
-
并发安全:方案 B 中合并步数使用短关键段即可,不会影响 FOC 实时性;DIR 翻转频率远低于 STEP,不会成为瓶颈。
选型结论
- 优先:方案 A(编码器模式)——硬件在每个 STEP 沿锁存 DIR,逻辑最干净、最接近工业伺服。
- 备选:方案 B(外部时钟 + DIR EXTI 结清)——当引脚受限或只能用 ETR/TI1 时,依然稳。
三、用STM32F405实现方案A
1) 硬件接线(以 TIM2 为例,32 位计数器,推荐)
- STEP →
TIM2_CH1
(PA0,AF1) - DIR →
TIM2_CH2
(PA1,AF1) - 建议:STEP、DIR 近 MCU 端各串 ~100 Ω + 100 pF 小 RC;长线优先差分/光耦隔离;MCU 端上拉/下拉择一(和对端一致)。
若 TIM2 被占用,可用 TIM5(同为 32 位):
STEP→TIM5_CH1
(PA0, AF2),DIR→TIM5_CH2
(PA1, AF2);代码里把TIM2
全部替换为TIM5
并改 AF 号。
2) 工作原理(编码器 TI1 模式)
把定时器设为 Encoder TI1:
- STEP(TI1)的上升沿每来一次,计数器 ±1;
- DIR(TI2)的电平在该沿被硬件锁存,决定是加还是减;
- 你在 FOC 周期里读
CNT
的增量就是净步数(带符号)。
这与工业伺服“Step/Dir 口 + 硬件方向锁存”一致,不用担心周期内 DIR 多次翻转。
3) 关键参数(按51200 步/圈)
// ---- 机械参数(按需修改)----
#define STEPS_PER_REV 51200.0f // 每圈步数(细分后)
#define GEAR_RATIO 1.0f // 减速比(电机轴到负载),有减速箱就写>1
// 方向是否取反(接线导致方向反了就置1)
#define INVERT_DIR 0// ---- 控制参数(示例)----
#define FOC_RATE_HZ 10000.0f // 你的FOC控制中断频率(例:10 kHz)// ---- 换算 ----
#define STEP_TO_RAD (2.0f * 3.14159265358979f / (STEPS_PER_REV * GEAR_RATIO))
#define STEPS_TO_RAD(steps) ((steps) * STEP_TO_RAD)
#define STEPS_TO_RAD_PER_SEC(steps_per_tick) ((steps_per_tick) * STEP_TO_RAD * FOC_RATE_HZ)
4) HAL 初始化代码(GPIO + TIM2 编码器模式)
放到你的
xxx_init.c
或者专用stepdir.c
里即可;GPIO 的 MSP 也可集中放在stm32f4xx_hal_msp.c
。
#include "stm32f4xx_hal.h"
#include <math.h>//二选一
TIM_HandleTypeDef htim2;
//TIM_HandleTypeDef htim5;// ========== GPIO: PA0/PA1 复用为 TIM2_CH1/CH2 ==========
static void StepDir_GPIO_Init_TIM2(void) {__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef G = {0};G.Mode = GPIO_MODE_AF_PP;G.Pull = GPIO_PULLDOWN; // 依现场电路定:上拉或下拉二选一,空闲低更稳G.Speed = GPIO_SPEED_FREQ_VERY_HIGH;G.Alternate = GPIO_AF1_TIM2;// STEP -> PA0 (TIM2_CH1)G.Pin = GPIO_PIN_0;HAL_GPIO_Init(GPIOA, &G);// DIR -> PA1 (TIM2_CH2)G.Pin = GPIO_PIN_1;HAL_GPIO_Init(GPIOA, &G);
}// ========== TIM2: 编码器模式(TI1计步,TI2判方向) ==========
void StepDir_Encoder_Init_TIM2(void) {__HAL_RCC_TIM2_CLK_ENABLE();StepDir_GPIO_Init_TIM2();htim2.Instance = TIM2;htim2.Init.Prescaler = 0; // 1:1htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 编码器模式下无实际影响htim2.Init.Period = 0xFFFFFFFF; // 32位满量程htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.RepetitionCounter = 0;TIM_Encoder_InitTypeDef enc = {0};enc.EncoderMode = TIM_ENCODERMODE_TI1; // 用TI1为时钟,TI2提供方向// TI1 = STEPenc.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 计上升沿(与上位保持一致)enc.IC1Selection = TIM_ICSELECTION_DIRECTTI;enc.IC1Prescaler = TIM_ICPSC_DIV1;enc.IC1Filter = 8; // 数字滤波:8~12 先试;若高频步进需减小该值// TI2 = DIR(作为方向电平被锁存)enc.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 极性对电平锁存无本质影响enc.IC2Selection = TIM_ICSELECTION_DIRECTTI;enc.IC2Prescaler = TIM_ICPSC_DIV1;enc.IC2Filter = 8;HAL_TIM_Encoder_Init(&htim2, &enc);TIM_MasterConfigTypeDef master = {0};master.MasterOutputTrigger = TIM_TRGO_RESET;master.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim2, &master);__HAL_TIM_SET_COUNTER(&htim2, 0);HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
}// ========== GPIO: PA0/PA1 复用为 TIM5_CH1/CH2 ==========
static void StepDir_GPIO_Init_TIM5(void) {__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef G = {0};G.Mode = GPIO_MODE_AF_PP;G.Pull = GPIO_PULLDOWN; // 依现场电路定:上拉或下拉二选一,空闲低更稳G.Speed = GPIO_SPEED_FREQ_VERY_HIGH;G.Alternate = GPIO_AF2_TIM5; // ★ TIM5 用 AF2// STEP -> PA0 (TIM5_CH1)G.Pin = GPIO_PIN_0;HAL_GPIO_Init(GPIOA, &G);// DIR -> PA1 (TIM5_CH2)G.Pin = GPIO_PIN_1;HAL_GPIO_Init(GPIOA, &G);
}// ========== TIM5: 编码器模式(TI1计步,TI2判方向) ==========
void StepDir_Encoder_Init_TIM5(void) {__HAL_RCC_TIM5_CLK_ENABLE(); // ★ TIM5 时钟StepDir_GPIO_Init_TIM5();htim5.Instance = TIM5;htim5.Init.Prescaler = 0; // 1:1htim5.Init.CounterMode = TIM_COUNTERMODE_UP; // 编码器模式下无实际影响htim5.Init.Period = 0xFFFFFFFF; // 32位满量程htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim5.Init.RepetitionCounter = 0;TIM_Encoder_InitTypeDef enc = {0};enc.EncoderMode = TIM_ENCODERMODE_TI1; // 用 TI1 为时钟,TI2 提供方向// TI1 = STEPenc.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 计上升沿(与上位保持一致)enc.IC1Selection = TIM_ICSELECTION_DIRECTTI;enc.IC1Prescaler = TIM_ICPSC_DIV1;enc.IC1Filter = 8; // 8~12 先试;要更高步频可适当减小// TI2 = DIR(方向电平被锁存)enc.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING; // 极性对电平锁存影响不大enc.IC2Selection = TIM_ICSELECTION_DIRECTTI;enc.IC2Prescaler = TIM_ICPSC_DIV1;enc.IC2Filter = 8;HAL_TIM_Encoder_Init(&htim5, &enc);TIM_MasterConfigTypeDef master = {0};master.MasterOutputTrigger = TIM_TRGO_RESET;master.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim5, &master);__HAL_TIM_SET_COUNTER(&htim5, 0);HAL_TIM_Encoder_Start(&htim5, TIM_CHANNEL_ALL);
}
关于滤波(ICxF):值越大抗毛刺越强,但可接受的最小脉宽越大(最高步频越低)。
一般先用 8~12 验证;若要跑更高频(>200 kHz),逐步调小滤波,或提升电气整形质量。
5) 读增量 & 注入 FOC(示例)
// 累计位置(以“步”为单位),用64位避免长时间溢出
static volatile int64_t s_pos_steps_accum = 0;
static uint32_t s_last_cnt = 0;// 每次FOC控制中断(例:10kHz)调用
static inline int32_t StepDir_ReadDeltaSteps(void)
{uint32_t now = __HAL_TIM_GET_COUNTER(&htim2);// 无符号差处理溢出,再转有符号就是带方向的增量int32_t delta = (int32_t)(now - s_last_cnt);s_last_cnt = now;#if INVERT_DIRdelta = -delta; // 方向反接时一键取反
#endifs_pos_steps_accum += delta;return delta;
}// 示例:在你的 FOC 周期函数里使用
void FOC_Control_ISR(void)
{int32_t dsteps = StepDir_ReadDeltaSteps();// 目标位置增量(弧度)float delta_pos_rad = STEPS_TO_RAD((float)dsteps);// 目标速度(弧度/秒),如需速度环float vel_ref_rad_s = STEPS_TO_RAD_PER_SEC((float)dsteps);// 将目标注入外环(按你的控制结构)// pos_ref += delta_pos_rad;// vel_ref = vel_ref_rad_s;// ... 之后进入速度/电流环计算(你的FOC实现)
}// 归零(回零完成后可调用)
static inline void StepDir_Zero(void)
{__HAL_TIM_SET_COUNTER(&htim2, 0);s_last_cnt = 0;s_pos_steps_accum = 0;
}
6) 常见问题与工程注意
-
DIR 建立时间
- 路线规划的主控板发送变向脉冲后,务必延时 ≥2–5 µs再发第一步(行业常规要求)。编码器模式会在STEP 的那个沿采样 DIR 电平,有这个建立时间就稳如老狗。
-
极性不一致
- 如果发现“DIR=正”却反向增加,最快的处理:
#define INVERT_DIR 1
; - 或改上位机 DIR 逻辑;或交换电机线相(若允许)。
- 如果发现“DIR=正”却反向增加,最快的处理:
-
最高步频与滤波
- 数字滤波值越大,允许的最小脉宽越大,从而限制最高频;
- 实测时从
ICxF=8~12
起步,观测丢步/毛刺情况,再折中到你需要的最高频; - 电气上尽量用差分/光耦、良好地线、靠近 MCU 的 RC 抑毛刺,可以让滤波开得更小从而上更高频。
-
计数位宽
- 用 TIM2/TIM5(32 位) 基本无需担心溢出;
- 依然推荐只在 FOC 周期用增量,而不是长期依赖绝对
CNT
。
-
对齐到机械/电角度
- 上面
STEP_TO_RAD
是机械角度换算。若你的 FOC 内部用电角度,乘以极对数即可。
- 上面
-
自检与诊断(可选)
- 联调阶段可用输入捕获+DMA记录若干 STEP 的时间戳,检查上位机步间隔抖动与
t_dir_setup
是否满足;量产时关闭。
- 联调阶段可用输入捕获+DMA记录若干 STEP 的时间戳,检查上位机步间隔抖动与
7) 如果改用 TIM5(引脚被占时)
把 GPIO AF 改为 AF2,其余相同:
// STEP -> PA0 (TIM5_CH1, AF2)
// DIR -> PA1 (TIM5_CH2, AF2)
G.Alternate = GPIO_AF2_TIM5; // 仅此处不同
// 然后把所有 TIM2 改成 TIM5
这样配置后,驱动板就拥有“工业伺服式 Step/Dir 接口”:
- A 板输出脉冲与方向;
- B 板硬件在每个 Step 沿锁存方向、计正负步;
- 你只在 FOC 周期读增量并换算成位置/速度即可。
8)IC1Filter 与 IC2Filter 介绍再调试技巧
主要就三件事:它滤的是什么、会牺牲什么、怎么按你目标步频和脉宽倒推数值。
它到底在做什么
-
ICxF 是定时器通道前端的“数字去抖滤波器”。
作用:只有当输入在某个采样频率f_sampling
下连续 N 次保持同一电平,才把这次变化“放行”为一个有效沿。 -
ICxF(4bit)并不是简单的 0~15 阶,而是选择两样东西:
- 采样频率
f_sampling = f_DTS / M
(M 为某个分频因子), - 需要连续通过的样本数
N
(通常取 2、4 或 8)。
- 采样频率
-
f_DTS
来源于定时器数字滤波时钟(由定时器时钟和CR1.CKD
派生)。 -
IC1Filter
作用在 STEP 通道(TI1),IC2Filter
作用在 DIR 通道(TI2)(编码器模式下,硬件在 STEP 的那个有效沿锁存此刻的 DIR 电平)。
结论:ICxF 越大 → 抗毛刺越强,但输入允许的最高频率越低、沿的传播延迟变大。参考手册 RM0090 的定时器章节有完整映射表(每个 ICxF 对应的
M
与N
组合)。(STMicroelectronics)
你能用的两条硬规则(很实用)
把你 A 板输出的 STEP 脉冲看成高/低各一段时间:
-
边沿“能被看见”的必要条件(不丢沿):
t_high ≥ N / f_sampling 且 t_low ≥ N / f_sampling
也就是高、低最短持续时间都要够“连过 N 个采样”。
-
50% 占空近似下的最高可通过频率(方便估算):
f_step_max ≈ f_sampling / (2N)
这两条直接把“滤波强度(N)/采样频率(f_sampling)”跟你的最短脉宽/最高步频绑在一起了。
怎么一步到位地“倒推”ICxF
-
量/定你的上限:
- 最高步频
f_step_target
; - 最短高/低电平宽度
t_min
(若不是 50% 占空就取两者中的较小值)。
- 最高步频
-
选一个 N:
- 工程上常用 N=4 起步(稳、延迟不大);
- 线缆很长/干扰大可上 N=8。
-
算需要的采样频率:
- 按“最短脉宽”保守:
f_sampling ≥ N / t_min × 安全系数(≈1.5~2)
; - 或按“最高步频”保守:
f_sampling ≥ 2N × f_step_target × 安全系数
。
- 按“最短脉宽”保守:
-
在手册表里挑 ICxF:
- 找到**≥ 目标 f_sampling**的那个
ICxF
组合(它对应某个分频M
和N
)。 - 同时别忘了
CR1.CKD
也会影响f_DTS
(一般保持 DIV1,让f_sampling
有更大余量)。(STMicroelectronics)
- 找到**≥ 目标 f_sampling**的那个
例子(演示思路):
假设 TIM5 时钟 ~84 MHz,CKD=DIV1
,你要撑到 200 kHz 的步频并留一倍余量,且选 N=4。
则f_sampling ≥ 2×4×200k×2 ≈ 3.2 MHz
。
你就在表里挑一个IC1Filter
让f_sampling
≥ 3.2 MHz(比如分频到 ~8–16 MHz 量级都够),再上机验证。
实操建议(不看表也能调起来)
-
编码器模式(方案A)
IC1Filter
(STEP)从 8~12 起步:通常能有效灭抖;若高频运行丢步,就逐步减小(比如 6、4、2、0)。IC2Filter
(DIR)可与IC1Filter
持平或略大 1~2 档:方向线通常比步线更怕毛刺。CR1.CKD
先用 DIV1,真的很吵再考虑加大 CKD(会整体放慢f_sampling
,要同步下调 ICxF)。
-
观察与对症
-
空闲时计数跳动/上电就到 0xFFFFFFFF:
- 给 STEP/DIR 设合适下拉(空闲低更稳)、增大 ICxF、按“先启用后清零”的顺序再清一次 CNT。
-
高速下丢步:
- 减小 IC1Filter 或把
CKD
调回 DIV1;同时确认 A 板脉宽是否达到驱动器规范(不少于 2–3 µs)。
- 减小 IC1Filter 或把
-
偶发错向/负跳:
- 增大 IC2Filter;检查
t_dir_setup
(变向到第一步的延时)是否 ≥ 2–5 µs。
- 增大 IC2Filter;检查
-
光耦尾沿回跳:
- 适当增大 ICxF 或加 RC/施密特;必要时把“有效沿”选在回跳更干净的边。
-
一个可复制的“调参流程”
-
先把 STEP/DIR 空闲电平稳定(多用
PULLDOWN
,空闲低+正脉冲最稳)。 -
IC1Filter=10
,IC2Filter=12
,CKD=DIV1
启动,空闲观察 CNT=0。 -
带着逻辑分析仪跑到目标最高步频:
- 若丢步 → 依次把
IC1Filter
降到 8、6、4… 直到满频不丢; - 若仍有毛刺/误计 → 把
IC2Filter
再提 1–2 档,必要时 STEP 也提一档并稍降最高步频或加硬件整形。
- 若丢步 → 依次把
-
定下来的
ICxF
记录在案,再按t_dir_setup
、脉宽裕量
做一次回归测试。
需要查表的时候
当你要精确算某个 ICxF
对应的 f_sampling/N
,或想用 ETR 的 ETF 做同样事情(方案B)时,直接开 RM0090 的定时器章节,里面有一张“ICxF/ETF 取值 → 采样分频 M & 连续样本数 N”的对照表;把你的定时器时钟、CKD
、该表三者代入上面的两个公式即可。(STMicroelectronics)