电机驱动实现插补算法之脉冲和方向接收(以stm32主控为例)

一、方案 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 试起,再按需要减小。

工程级细节与建议(两方案通用)

  1. 计数位宽:用 TIM2/TIM5(32 位) 把溢出顾虑降到最低;若被占用,用 16 位计数器也行,但要在 FOC 周期里读增量避免溢出。

  2. 单位换算

    // 步 -> 机械角度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 = 控制周期频率
    
  3. 极性/方向:若方向与期望相反,优先改上游 DIR 极性;或在本地把 g_dir_sign 取反/交换 TI 极性。

  4. 毛刺与布线:长线优先差分、近端 RC、上/下拉一致;数字滤波尽量启用(ICxF/ETF)。

  5. 并发安全:方案 B 中合并步数使用短关键段即可,不会影响 FOC 实时性;DIR 翻转频率远低于 STEP,不会成为瓶颈。


选型结论

  • 优先:方案 A(编码器模式)——硬件在每个 STEP 沿锁存 DIR,逻辑最干净、最接近工业伺服。
  • 备选:方案 B(外部时钟 + DIR EXTI 结清)——当引脚受限或只能用 ETR/TI1 时,依然稳。

三、用STM32F405实现方案A

1) 硬件接线(以 TIM2 为例,32 位计数器,推荐)

  • STEPTIM2_CH1(PA0,AF1)
  • DIRTIM2_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) 常见问题与工程注意

  1. DIR 建立时间

    • 路线规划的主控板发送变向脉冲后,务必延时 ≥2–5 µs再发第一步(行业常规要求)。编码器模式会在STEP 的那个沿采样 DIR 电平,有这个建立时间就稳如老狗。
  2. 极性不一致

    • 如果发现“DIR=正”却反向增加,最快的处理:#define INVERT_DIR 1
    • 或改上位机 DIR 逻辑;或交换电机线相(若允许)。
  3. 最高步频与滤波

    • 数字滤波值越大,允许的最小脉宽越大,从而限制最高频;
    • 实测时从 ICxF=8~12 起步,观测丢步/毛刺情况,再折中到你需要的最高频;
    • 电气上尽量用差分/光耦、良好地线、靠近 MCU 的 RC 抑毛刺,可以让滤波开得更小从而上更高频。
  4. 计数位宽

    • TIM2/TIM5(32 位) 基本无需担心溢出;
    • 依然推荐只在 FOC 周期用增量,而不是长期依赖绝对 CNT
  5. 对齐到机械/电角度

    • 上面 STEP_TO_RAD机械角度换算。若你的 FOC 内部用电角度,乘以极对数即可。
  6. 自检与诊断(可选)

    • 联调阶段可用输入捕获+DMA记录若干 STEP 的时间戳,检查上位机步间隔抖动与 t_dir_setup 是否满足;量产时关闭。

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 阶,而是选择两样东西

    1. 采样频率 f_sampling = f_DTS / M(M 为某个分频因子),
    2. 需要连续通过的样本数 N(通常取 2、4 或 8)。
  • f_DTS 来源于定时器数字滤波时钟(由定时器时钟和 CR1.CKD 派生)。

  • IC1Filter 作用在 STEP 通道(TI1)IC2Filter 作用在 DIR 通道(TI2)(编码器模式下,硬件在 STEP 的那个有效沿锁存此刻的 DIR 电平)。

结论:ICxF 越大 → 抗毛刺越强,但输入允许的最高频率越低、沿的传播延迟变大。参考手册 RM0090 的定时器章节有完整映射表(每个 ICxF 对应的 MN 组合)。(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

  1. 量/定你的上限:

    • 最高步频 f_step_target
    • 最短高/低电平宽度 t_min(若不是 50% 占空就取两者中的较小值)。
  2. 选一个 N

    • 工程上常用 N=4 起步(稳、延迟不大);
    • 线缆很长/干扰大可上 N=8
  3. 算需要的采样频率

    • 按“最短脉宽”保守:f_sampling ≥ N / t_min × 安全系数(≈1.5~2)
    • 或按“最高步频”保守:f_sampling ≥ 2N × f_step_target × 安全系数
  4. 在手册表里挑 ICxF

    • 找到**≥ 目标 f_sampling**的那个 ICxF 组合(它对应某个分频 MN)。
    • 同时别忘了 CR1.CKD 也会影响 f_DTS(一般保持 DIV1,让 f_sampling 有更大余量)。(STMicroelectronics)

例子(演示思路):
假设 TIM5 时钟 ~84 MHz,CKD=DIV1,你要撑到 200 kHz 的步频并留一倍余量,且选 N=4
f_sampling ≥ 2×4×200k×2 ≈ 3.2 MHz
你就在表里挑一个 IC1Filterf_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)。
    • 偶发错向/负跳

      • 增大 IC2Filter;检查 t_dir_setup(变向到第一步的延时)是否 ≥ 2–5 µs。
    • 光耦尾沿回跳

      • 适当增大 ICxF 或加 RC/施密特;必要时把“有效沿”选在回跳更干净的边。

一个可复制的“调参流程”

  1. 先把 STEP/DIR 空闲电平稳定(多用 PULLDOWN,空闲低+正脉冲最稳)。

  2. IC1Filter=10IC2Filter=12CKD=DIV1 启动,空闲观察 CNT=0

  3. 带着逻辑分析仪跑到目标最高步频:

    • 若丢步 → 依次把 IC1Filter 降到 8、6、4… 直到满频不丢;
    • 若仍有毛刺/误计 → 把 IC2Filter 再提 1–2 档,必要时 STEP 也提一档并稍降最高步频或加硬件整形。
  4. 定下来的 ICxF 记录在案,再按 t_dir_setup脉宽裕量 做一次回归测试。


需要查表的时候

当你要精确算某个 ICxF 对应的 f_sampling/N,或想用 ETR 的 ETF 做同样事情(方案B)时,直接开 RM0090 的定时器章节,里面有一张“ICxF/ETF 取值 → 采样分频 M & 连续样本数 N”的对照表;把你的定时器时钟、CKD、该表三者代入上面的两个公式即可。(STMicroelectronics)


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94056.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[特殊字符] 潜入深渊:探索 Linux 内核源码的奇幻之旅与生存指南

文章目录 朋友们&#xff0c;敲黑板&#xff01;&#xff01;&#xff01;&#xff08;超级重要&#xff09;我们今天聊点硬核的——不是普通的代码&#xff0c;而是驱动了整个数字世界心跳的Linux内核源代码&#xff01;它藏在哪&#xff1f;就在那个传奇仓库&#xff1a;torv…

如何监控和管理微服务之间的调用关系

监控和管理微服务之间的调用关系需要结合分布式追踪、服务依赖分析、实时监控和可视化工具&#xff0c;以实现对调用链路的可见性、问题定位和性能优化。以下是具体的实现方案和工具链&#xff1a;一、核心监控目标调用链路可视化&#xff1a;展示服务间的调用关系、方向和频率…

3.4 缩略词抽取

概述 缩略词指的是一个词或者短语的缩略形式&#xff0c;其通常由原词中的一些组成部分构成&#xff0c;同时保持原词的含义。缩略词的检测与抽取在方法上与同义词的检测与抽取类似&#xff0c;但是相比同义词&#xff0c;缩略词在文本中出现的规则往往更简单。 不同语言缩略词…

Lua脚本如何执行主程序的C函数

Lua Call C function Lua脚本可以和C函数灵活的结合&#xff0c;Lua脚本作为控制语言&#xff0c;使用C函数来做性能计算部分。 Lua脚本的执行器有Lua 和 Luajit。 使用Lua执行器&#xff0c;可以通过C语言注册C函数到Lua State, 然后Lua脚本可以调用该C函数。 使用Luajit&…

农业-学习记录

1-git上传&#xff08;简易版&#xff09;首先&#xff0c;记得vscode打开的项目路径是/home/Agribrain。不然会提示上传很多项目无关的文件。① 暂存所有更改②使用vscode终端注意&#xff1a;终端打开路径&#xff1a;/home/Agribrain/agribrain【git项目的所在目录】&#x…

什么是数据集成?数据集成对数据治理有什么影响

在大数据与人工智能驱动的时代&#xff0c;数据已经成为企业的“新型生产力”。然而&#xff0c;企业内部数据往往分散在不同系统、不同格式、甚至不同地域中。如果缺乏有效管理与整合&#xff0c;数据价值就无法真正释放。这时&#xff0c;“数据集成”应运而生&#xff0c;它…

技术成长战略是什么?

文章目录技术成长战略是什么&#xff1f;1. 前言2. 跟技术大牛学成长战略2.1 系统性能专家案例2.2 从开源到企业案例2.3 技术媒体大V案例2.4 案例小结3. 学习金字塔和刻意训练4. 战略思维的诞生5. 建议技术成长战略是什么&#xff1f; 1. 前言 在波波的微信技术交流群里头&am…

从0到1打造一台机器人走起来

聚焦仿人双足机器人,着重解决其下肢鲁棒行走中仿真到实机间隔(SimToReal gap)的误差问题 总述 硬件:采用傅利叶智能科技一体化关节模组: 1)胯部和膝关节选用 FSA80 - 29E 电机,以承受较大扭矩; 2)大腿部分采用 FSA60 - 43E 电机,兼顾扭矩和转速需求; 3)小腿选用 …

【Cmake】Cmake概览

目录 一.环境准备 1.1.Cmake安装 1.2. VSCodeCMake插件安装 1.3 快速样例-helloworld⼯程 二. cmake的基础命令⾏使用示例 2.1.文件准备 2.2.⽣成构建系统 2.3.编译连接 2.4.测试Ctest模块 2.5.测试安装模块 2.6.测试打包模块 2.7 查看帮助 CMake语法简洁清晰&…

概率核心概念学习笔记:随机事件与样本空间、古典概率与条件概率、全概率公式与贝叶斯公式

目录 一、 随机事件与样本空间 1. 原理讲解 2. 类型与关系 3. 案例计算 4. 应用场景 二、 古典概率与条件概率 1. 古典概率 (Classical Probability) 2. 条件概率 (Conditional Probability) 三、 全概率公式与贝叶斯公式 1. 全概率公式 (Law of Total Probability) …

优考试局域网系统V6.0.0版

优考试局域网系统迎来V6.0.0版本更新&#xff0c;核心在于提升功能性能与优化操作体验。重点对学情分析、移动端考试支持、考试监控和答题体验等方面进行了实用性更新&#xff0c;进一步提升了局域网环境下考试系统的灵活性与管理效率。 一、增加学情分析功能&#xff0c;教学…

Autosar之Com模块

Com模块主要实现了Signal在I-PDU中的封装及解析功能,为RTE层提供了基于Signal的发送与接收接口,实现了基于Signal的网关功能,实现了PDU的不同发送模式,以及Signal滤波,Update bit,Pdu Counter等功能 图 Com模块层次图 Com模块处于AUTOSAR架构中的通信服务层,其下层模块…

【iOS】NSRunLoop

目录 概念 RunLoop与线程的关系 Runloop对外的接口 CFRunLoopSourceRef Source0 Source1 CFRunLoopTimer CFRunLoopObserver RunLoop的Mode 应用场景 Runloop的内部逻辑 Runloop应用 tableView延迟加载图片&#xff0c;保证流畅 Timer不被ScrollView的滑动影响 A…

HTTP接口鉴权方式

几种主流且可行的HTTP接口鉴权方式&#xff0c;从简单到复杂&#xff0c;各有其适用场景。我将它们分为两大类&#xff1a;传统方式和现代方式。一、传统方式这类方式简单易用&#xff0c;但通常安全性较低或扩展性较差&#xff0c;适用于内部系统或简单API。1. HTTP Basic Aut…

DIC技术极端环境案例分享:系泊链在海水环境下氢脆化性能测试

实验结果的具体视频可详见以下链接&#xff1a;研索仪器DIC技术在极端条件下的应用 01 海水环境&#xff1a; DIC技术在海水环境下的应用核心挑战在于恶劣的光学条件&#xff08;如散射、衰减、畸变&#xff09;、严酷的化学/生物环境&#xff08;腐蚀、生物污损&#xff09;…

DL00291-联邦学习以去中心化锂离子电池健康预测模型完整实现

联邦学习在锂离子电池健康预测中的应用&#xff1a;去中心化训练与客户选择策略在锂离子电池健康预测领域&#xff0c;随着电池使用环境的多样化以及电池状态监测需求的不断增长&#xff0c;传统的集中式数据训练方法逐渐显现出局限性。为了解决数据隐私保护和大规模数据集中处…

TCP协议大全

什么是TCP&#xff1f;基本定义与属性TCP&#xff08;传输控制协议&#xff09;是传输层的重要协议&#xff0c;具有面向连接&#xff08;传输前需先建立连接&#xff0c;是发送方和接收方的点对点一对一连接&#xff09;、基于字节流&#xff08;以字节流形式传输数据&#xf…

当硅基生命遇见碳基萌宠:Deepoc具身智能如何重新定义“宠物监护者”

在东京某高级公寓里&#xff0c;一只布偶猫正优雅地踱步到智能喂食器前。令人惊讶的是&#xff0c;这个通体雪白的喂食器突然"活"了过来——它微微倾斜身体&#xff0c;用柔和的机械音发出问候&#xff0c;同时伸出仿生机械臂轻轻抚过猫咪的背部。这不是科幻电影场景…

线上日志排查问题

1、查异常堆栈 显示该行及其后面的50行内容&#xff0c;然后通过 less 命令进行分页查看 grep -A 50 "NullPointerException" a.log | less参数解释&#xff1a; grep: 文本搜索命令-A 50: After 的意思&#xff0c;显示匹配行后面的50行“NullPointerException”: 要…

LabVIEW与CAN开发燃料电池监控

​基于 LabVIEW 与 CAN 总线技术&#xff0c;构建了一套多组质子交换膜燃料电池&#xff08;PEMFC&#xff09;堆监控系统。系统采用优质硬件设备&#xff0c;通过 LabVIEW 的图形化编程能力实现数据采集、实时监控与多堆切换控制&#xff0c;稳定可靠&#xff0c;为燃料电池性…