步进电机完整驱动指南:BSP 驱动层到运动控制算法

Babel36acl 嵌入式实战 无~ 10 次阅读 预计阅读时间: 10 分钟 发布于 1 天前 最后更新于 1 小时前 2176 字


步进电机驱动完整指南:从 BSP 驱动层配置、TMC2209 UART 通信到运动控制算法(S 曲线启停、抛物线起步、motion_seen 防残留)的全覆盖。

一、BSP 驱动层

1.1 硬件配置

基于 STM32F103RCT6,单 TIM4 四通道驱动四轴步进电机:

flowchart TB
    subgraph BSP[BSP 驱动层]
        B1[TIM4 四通道PWM]
        B2[TMC2209 UART]
    end
    subgraph ALGO[运动算法]
        A1[S 曲线启停]
        A2[抛物线起步]
        A3[motion_seen 防残留]
    end
    subgraph CTRL[控制层]
        C1[速度规划]
        C2[脉冲计数]
    end
    BSP --> ALGO --> CTRL
    style BSP fill:transparent,stroke:#8dc7ff,color:#eaf4ff
    style ALGO fill:transparent,stroke:#8dc7ff,color:#eaf4ff
    style CTRL fill:transparent,stroke:#8dc7ff,color:#eaf4ff
  • TIM4:32 位向上计数,ARR 决定脉冲频率
  • CH1~CH4:四路独立 OC 比较输出,各驱动一台步进电机
  • TMC2209:通过 UART 单线协议配置(细分、电流、微步模式)
  • STEP/DIR/EN:各轴独立引脚

1.2 TMC2209 UART 配置

// TMC2209 单线 UART 写操作
static HAL_StatusTypeDef tmc2209_write_reg(uint8_t addr, uint32_t data) {
    uint64_t datagram = tmc2209_build_datagram(addr, data, TMC_WRITE);
    // 通过 UART TX 引脚发送 80 位数据帧
    // 格式: sync(0xFF) + addr(8) + data(32) + crc(8)
    // 使用软件 bit-bang 或硬件 UART 单线模式
    return tmc2209_send_datagram(datagram);
}

// 关键配置
tmc2209_write_reg(GCONF, 0x00000004);     // pdn_disable = 1, enable UART
tmc2209_write_reg(CHOICE, 0x00040100);     // microsteps = 16, vsense = 1
tmc2209_write_reg(TPOWERDOWN, 0x00000010); // 自动减电流延迟

1.3 四轴原子化启停

所有轴同时 start/stop 避免时序差导致机械偏移:

typedef struct {
    uint32_t arr;      // TIM4 ARR 值(决定脉冲频率)
    uint32_t pulse_target; // 目标脉冲数
    uint32_t pulse_emitted; // 已发出脉冲数
    uint8_t  step_pin;     // STEP 引脚掩码
    uint8_t  dir_pin;      // DIR 引脚
    bool     running;
} stepper_axis_t;

static stepper_axis_t axes[STEPPER_AXIS_MAX];

void stepper_start_all(uint32_t freq_hz, uint32_t pulses) {
    // 1. 计算 ARR = (TIM_CLOCK / (prescaler * freq)) - 1
    // 2. 写入所有轴的目标脉冲数
    // 3. 统一启动 TIM4
    TIM4->ARR = arr_value;
    TIM4->CR1 |= TIM_CR1_CEN;  // 所有轴同时开始
}

void stepper_stop_all(void) {
    TIM4->CR1 &= ~TIM_CR1_CEN; // 所有轴同时停止
}

1.4 轮询驱动(task_once 模式)

void stepper_task_once(void) {
    uint32_t sr = TIM4->SR;
    for (int i = 0; i < STEPPER_AXIS_MAX; i++) {
        if (!axes[i].running) continue;
        if (sr & (TIM_SR_CC1IF << i)) {  // 该通道捕获/比较事件
            HAL_GPIO_TogglePin(axes[i].step_port, axes[i].step_pin);
            axes[i].pulse_emitted++;
            if (axes[i].pulse_emitted >= axes[i].pulse_target) {
                axes[i].running = false;
                axes[i].done = true;
            }
        }
    }
}

二、S 曲线启停

2.1 问题

减速段末端脉冲频率极低,如果等待最后几个慢脉冲走完才置 done,会导致明显的停顿感。

2.2 解法:优先置 done

在 task_once 中检查 emitted ≥ target 时立即 stop + done,不等剩余慢脉冲走完。因为末尾段脉冲已慢到不产生实际位移,直接截断完全可接受。

void stepper_task_once(void) {
    // ... 正常脉冲输出
    if (axes[i].pulse_emitted >= axes[i].pulse_target) {
        axes[i].running = false;
        axes[i].done = true;  // 立刻标记完成,不等最后脉冲
    }
}

2.3 注意事项

  • 仅适用于减速段末端脉冲极慢的场景
  • 需要配合 motion_seen 防残留机制使用(见下节)
  • 如果应用要求精确计步(如定位),不建议截断

三、抛物线起步与 motion_seen 防残留

3.1 二次抛物线起步

开环步进电机在启动瞬间容易丢步,二次抛物线软启动通过逐步增加脉冲频率避免:

// 抛物线加速:f(t) = a * t²
// 每 tick 计算当前频率
static uint32_t calc_parabola_freq(uint32_t tick, uint32_t target_freq, uint32_t accel_time_ms) {
    float progress = (float)tick / (float)accel_time_ms;
    if (progress >= 1.0f) return target_freq;
    return (uint32_t)((float)target_freq * progress * progress);
}

3.2 motion_seen 防残留

问题:S 曲线启停中优先置 done 的做法,会让前一次运动的 done 标志残留到下一次。新运动开始时,如果直接检查 done 标志,会误以为上一次运动还没结束。

解法:引入 motion_seen 标志——每次新运动开始时先清 motion_seen,等待第一次真正发出脉冲后再置位。状态机检查时用 motion_seen 而非 done:

static bool motion_seen[STEPPER_AXIS_MAX] = {false};

void stepper_start_new_move(uint8_t axis) {
    motion_seen[axis] = false;  // 新运动开始,清标志
    axes[axis].pulse_emitted = 0;
    axes[axis].pulse_target = target;
    axes[axis].running = true;
}

void stepper_task_once(void) {
    if (/* 发出一次脉冲 */) {
        motion_seen[axis] = true;  // 确认已开始运动
    }
}

// 外部状态机判断运动完成
bool stepper_is_motion_done(uint8_t axis) {
    return motion_seen[axis] && !axes[axis].running;
}

3.3 搭配使用

这两个技巧必须一起用:S 曲线优先置 done = 不等末尾慢脉冲;motion_seen = 防止 done 残留影响下一次判据。缺一个就会出现误判。

四、完整集成:驱动 + 控制算法

在实际项目中,BSP 驱动层和运动控制算法层通过统一动作表调度表链接:

// 动作表条目
typedef struct {
    uint8_t  axis;        // 轴号
    uint32_t target_pulse; // 目标脉冲数
    uint32_t freq_hz;      // 目标频率
    uint32_t accel_ms;     // 加速时间(ms)
    stepper_mode_t mode;   // S曲线/抛物线/恒速
} action_entry_t;

// 动作表驱动
void action_table_execute(action_entry_t *table, uint8_t count) {
    for (int i = 0; i < count; i++) {
        if (table[i].mode == STEPPER_MODE_PARABOLA) {
            stepper_start_parabola(table[i].axis,
                table[i].target_pulse, table[i].freq_hz, table[i].accel_ms);
        } else {
            stepper_start_s_curve(table[i].axis,
                table[i].target_pulse, table[i].freq_hz);
        }
    }
}

相关文章

此作者没有提供个人介绍。
最后更新于 2026-05-30