这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 静音步进电机控制实践-成果贴-滑轨往复运动与速度控制实现

共1条 1/1 1 跳转至

静音步进电机控制实践-成果贴-滑轨往复运动与速度控制实现

高工
2026-06-30 23:48:32     打赏

        第二阶段成果交付物:在第一阶段"按键启停"基础上,加入 方向控制(往复运动) 和 速度控制(UART 调速),并用 TIM12 提供 10 ms 时基来调度相位切换。

1. 任务要求 → 实现映射

    
任务要求实现位置关键 API
按键启动/停止滑轨运动BSP_PB_Callbackmotion_toggle()
单次往复:CW 3 s → CCW 6 s → CW 3 s,回到原点阶段脉冲数常量motion_start()motion_tick() 状态机
TIM12 10 ms 定时(替代脉冲计数)PSC=249, ARR=9999HAL_TIM_Base_Start_IT(&htim12)
UART 速度切换,5 档(1=1 kHz … 5=5 kHz)motion_poll_uartmotion_set_speed(level)
速度切换后每相时长成比例变化,距离不变 脉冲数恒定换算ticks = pulses × 100 / freq_hz

2. 与第一阶段的差异

维度第一阶段第二阶段
电机行为按一次转、再按一次停(连续转)按一次触发"3-6-3 往复循环",结束后自动停
方向控制固定 CW启动 → CW → CCW → CW 自动翻转
速度固定 5 kHz5 档(1–5 kHz),UART 实时切换
时基无(纯 PWM)TIM12 每 10 ms 进中断
软件模块motormotor + motion
while(1)空转motion_poll_uart() 轮询串口

3. 软件架构

+---------------------------+
                     |       BSP_PB_Callback     |  EXTI13 (PC13 上升沿)
                     +-------------+-------------+
                                   |
                                   v   按一次键
                     +---------------------------+
                     |       motion_toggle()     |
                     +-------------+-------------+
                                   |
                                   v
+-------------+    +---------------------------+    +-----------------+
|  UART RX    |--->|     motion_poll_uart()    |--->| motion_set_speed|
|  (hcom_uart)|    |  (主循环每周期调用一次)    |    +-----------------+
+-------------+    +-------------+-------------+
                                   ^ '1'..'5'
                                   |
+-------------+    +---------------------------+    +-----------------+
|  TIM12 ISR  |--->|       motion_tick()       |--->|  phase machine  |
|  (每10ms)   |    |  ticks_left-- 到 0 切相   |    +-----------------+
+-------------+    +-------------+-------------+
                                   |
                                   v
                     +---------------------------+
                     |        motor 模块         |
                     |  start / stop / set_dir / |
                     |  set_freq                 |
                     +-------------+-------------+
                                   |
                                   v
            TIM3_CH3 PWM (PC8) + EN (PB15) + DIR (PB14)
                                   |
                                   v
                          TMC2209 SILENTSTEPSTICK
                                   |
                                   v
                              步进电机 → 滑轨

        外部事件三路(按键、TIM12 滴答、UART 字节)通过三个独立入口进入 motion 模块。

4. 新增模块详解

状态机
motion_start()
        IDLE  ----------------->  RUNNING / PHASE_FWD_1
          ^                            |
          |                            | motion_tick: ticks_left==0
          |                            v
          |                       PHASE_REV_1
          |                            |
          |                            | ticks_left==0
          |                            v
          |                       PHASE_FWD_2
          |                            |
          +------------  motion_stop()|ticks_left==0
                                       v
                                   motion_stop()

        四个 phase 对应三个运动段,phase0 (PHASE_IDLE) 仅作为未启动的标记。

相位常量与时长换算
#define PHASE1_PULSES   15000UL   /*  3 s @ 5 kHz → +15000 */
#define PHASE2_PULSES   30000UL   /*  6 s @ 5 kHz → -30000 */
#define PHASE3_PULSES   15000UL   /*  3 s @ 5 kHz → +15000 */
                                   /* net = 0,回到原点 */

        phase_duration_ticks() 把"X 脉冲 at f Hz"换算成"Y 个 10 ms tick":

return (pulses * 100UL) / freq_hz;   /* = pulses/(f·0.01) */

        之所以这么写而不是 pulses / freq * 100:避免 32 位除法的精度损耗(先除再乘可能掉 1)。5 档频率(1000/2000/3000/4000/5000)都能整除 15000 和 30000,不会出现 +1 tick 的累计误差。

速度档freqphase1phase2phase3总循环
11 kHz15 s30 s15 s60 s
22 kHz7.5 s15 s7.5 s30 s
33 kHz5 s10 s5 s20 s
44 kHz3.75 s7.5 s3.75 s15 s
55 kHz3 s6 s3 s12 s
速度切换时的相位重缩放
void motion_set_speed(uint8_t level)
{
    if (level < MOTION_SPEED_MIN || level > MOTION_SPEED_MAX) return;
    s_motion.speed_level = level;
    motor_set_freq(kSpeedHz[level - 1U]);

    /* 若当前有 phase 在飞,重新计算剩余 tick 数 */
    if (s_motion.state == MOTION_RUNNING && s_motion.phase != PHASE_IDLE)
    {
        s_motion.ticks_left = phase_duration_ticks(s_motion.phase);
    }
}

        这样:当前相位剩余的脉冲数 = ticks_left × freq × 0.01,换档后用新 freq 反算 ticks,使每个 phase 的脉冲数仍贴近目标值。整 phase 切完之后,后续 phase 直接以新档位计算——所以"换档后每个 phase 走相同的物理距离"严格成立。

串口轮询
void motion_poll_uart(void)
{
    uint8_t ch;
    if (HAL_UART_Receive(&hcom_uart[COM1], &ch, 1U, 0U) != HAL_OK) return;
    if (ch >= '1' && ch <= '5') {
        motion_set_speed((uint8_t)(ch - '0'));
        printf("speed=%u  freq=%lu Hz\r\n", ...);
    }
}
要点:
  • 非阻塞:HAL_UART_Receive(..., timeout=0),主循环调用一次只花几微秒

  • 使用 BSP 已初始化的句柄:hcom_uart[COM1](USART2,115200,PA2/PA3),无需自己再配 NVIC/GPIO

  • 回显:用 printf 把当前档位和实际频率写回 VCP,串口终端能直接看到

  • 过滤:只接受 '1'..'5',其他字节丢弃;不需要 buffer(单字节命令)

TIM12 时基

        IOC 配置:
TIM12 internal clock
   ├── Prescaler = 249     → 1 tick = 1 µs (250 MHz / 250)
   └── Period    = 9999    → 10000 ticks = 10 ms
   └── Interrupt enabled

        中断入口已由 CubeMX 生成,只做一件事:HAL_TIM_IRQHandler(&htim12)。HAL 把通用中断分派到 HAL_TIM_PeriodElapsedCallback:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM12) motion_tick();
}
这样设计的好处:
  • 调度逻辑全部封装在 motion 模块里,main.c 不需要知道 10 ms tick 怎么变成 phase 切换

  • 未来如果换 SysTick、换 DMA 定时、或者改成 1 ms tick,只动 motion 模块就行

  • HAL 回调是 weak 符号,可以多个外设共用一个分发函数

按键回调升级

        第一阶段的 BSP_PB_Callback 调 motor_toggle(),第二阶段改成 motion_toggle():

void BSP_PB_Callback(Button_TypeDef Button)
{
    if (Button == BUTTON_USER) {
        motion_toggle();
        BSP_LED_On(LED_GREEN);
        for (volatile uint32_t d = 0; d < 200000; d++) { __NOP(); }
        BSP_LED_Off(LED_GREEN);
    }
}

        motion_toggle() 内部判断:如果在 RUNNING 就 stop,否则 start。所以行为是:

  • 没启动 → 按一下 → 启动一次"3-6-3 循环",到点自动停

  • 在循环运行中 → 按一下 → 立即停止(停在当前位置,不补完剩余 phase)

  • 跑完一次循环后 → 按一下 → 重新启动新一轮

5. 关键设计权衡

为什么用 10 ms tick 而不是脉冲计数

        由于我们使用的 PWM 输出,不能完全记录脉冲数,采用定时定时的方式,进行运动控制"。TMC2209 的 STEP 输入接的是 TIM3_CH3 PWM,电机收到的每个脉冲都在硬件里自动消化,主核既看不到上升沿也读不到计数器。所以"跑了多少步"这个问题在硬件层面就是不可知的。换成时间可以通过时间进行阶段控制,保证能回到原点。

6. 构建与资源

[10/10] Linking C executable H533_motor.elf
Memory region    Used Size   Region Size   %age Used
          RAM:        2384 B       272 KB      0.86%
        FLASH:       34792 B       512 KB      6.64%

        对比第一阶段(RAM 1864 B / FLASH 27556 B):

  • RAM +520 B:motion_t 状态结构 + UART 接收缓冲(HAL_UART_Receive 内部用)

  • FLASH +7.2 KB:motion.c 全部代码 + printf 浮点格式化字符串

        整体仍处于个位数百分比的占用率,还有大量余量。

7.效果展示和讲解

【滑轨往复运动与速度控制实现】 https://www.bilibili.com/video/BV1HMTw6cEiw/?share_source=copy_web&vd_source=b04da51472e5874aa452f4a273672c46






关键词: 往复     运动     成果    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]