一、方案 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)