接下来就是最后功能实现的时刻了,我们本次主要实现的功能想要都融合在一起,不管是启停还是方向控制还有速度控制,希望都可以用这个开发板的现有资源实现,也就是说尽量不再进行额外的硬件资源,当然了步进电机是必不可少的。
这次主要的控制对象就是步进电机,我们找了一个42步进电机,作为本次的控制目标:


注意电机的主要供电是需要单独供电的。
步进电机控制软件功能实现通过板载用户按键(PC13)循环切换电机工作状态,所有电机动作由 TIM12 1ms 中断驱动:
硬件资源
软件架构
整体由两条中断路径驱动,主循环保持空闲:
+-------------------+ +----------------------+ 按键 PC13 ─► EXTI13_IRQHandler ──► BSP_PB_Callback ──► Motor_CycleState() |(200ms 消抖) | |(切换 state, 重置相位)| +-------------------+ +----------------------+ │ ▼ +-------------------+ +----------------------+ TIM12 1ms ─► TIM12_IRQHandler ──► HAL_TIM_PeriodElapsedCallback +-------------------+ |(生成 STEP 脉冲,自动换向)| +----------------------+
功能函数详解
Motor_CycleState() — 按键事件处理
当EXTI13中断触发时,依次调用BSP_PB_Callback函数,进而调用Motor_CycleState功能。依据当前状态来计算下一状态,达成STOPPED → SPEED1 → SPEED2 → STOPPED的循环转换。关键步骤:借助motor.state,利用switch语句切换至下一状态,并同步设定 motor.period_us 的值。重置步进相位:将phase、acc_us、pulse_ticks、settling_ticks、steps_in_dir以及 dir_change_pending全部清零,以此保证在状态切换后,首个STEP脉冲能够干净利落地启动,不会携带上一个状态的残余脉冲。调用 Motor_ApplyStateGpio() 函数,同步更新 EN 与 LED 的状态。设计意义:在速度切换过程中,新的 period_us 仅在相位重置后的下一次 READY 开始计时时才会生效,如此一来,当前正在输出的脉冲不会被中途打断,从而实现平稳的速度过渡。
代码如下:
static void Motor_CycleState(void)
{
/* 根据当前状态计算下一状态 */
switch (motor.state)
{
case MOTOR_STATE_STOPPED:
motor.state = MOTOR_STATE_SPEED1;
motor.period_us = MOTOR_STEP_PERIOD_S1_US;
break;
case MOTOR_STATE_SPEED1:
motor.state = MOTOR_STATE_SPEED2;
motor.period_us = MOTOR_STEP_PERIOD_S2_US;
break;
case MOTOR_STATE_SPEED2:
default:
motor.state = MOTOR_STATE_STOPPED;
break;
}
/* 切换状态后重置步进相位,避免速度切换/启动时脉冲被截断导致丢步 */
motor.phase = MOTOR_PHASE_READY;
motor.acc_us = 0;
motor.pulse_ticks = 0;
motor.settling_ticks = 0;
motor.steps_in_dir = 0;
motor.dir_change_pending = 0;
/* 速度切换时,新的 period_us 在下一次 READY 相位开始计时时生效,
这样当前正在输出的脉冲不会被中途打断,实现平稳过渡 */
Motor_ApplyStateGpio();
}Motor_SetDirection(Motor_Dir_t new_dir) — 安全换向
仅当TIM12中断服务程序(ISR)处于READY阶段,且STEP信号确定处于低电平时,方可进行调用。执行motor.dir的翻转操作,并将翻转后的值写入DIR引脚。在调用此功能前,必须确保STEP引脚为低电平状态,以防止在STEP信号的边沿附近更改DIR设置,进而引发TMC2209芯片内部时序出现错乱或导致丢步现象。
代码如下:
tatic void Motor_SetDirection(Motor_Dir_t new_dir)
{
motor.dir = new_dir;
HAL_GPIO_WritePin(Dir_GPIO_Port, Dir_Pin,
(new_dir == MOTOR_DIR_CW) ? GPIO_PIN_RESET : GPIO_PIN_SET);
}Motor_ApplyStateGpio() — 状态同步到 GPIO / LED
该功能依据 motor.state 的状态进行同步操作。当处于STOPPED状态时,将EN设置为HIGH(鉴于TMC2209为低有效,此操作可禁用驱动器),同时让LED熄灭(LED_OFF);当处于SPEED1或SPEED2状态时,把EN设置为LOW(使能驱动器),并点亮LED(LED_ON)。此功能可被 Motor_CycleState() 函数以及TIM12中断服务程序(ISR)调用,以此保证在停止状态下,EN与LED的状态始终得以维持。
代码如下:static void Motor_ApplyStateGpio(void)
{
if (motor.state == MOTOR_STATE_STOPPED)
{
HAL_GPIO_WritePin(En_GPIO_Port, En_Pin, GPIO_PIN_SET);
BSP_LED_Off(LED_GREEN);
}
else
{
HAL_GPIO_WritePin(En_GPIO_Port, En_Pin, GPIO_PIN_RESET);
BSP_LED_On(LED_GREEN);
}
}HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) — 核心ISR
代码如下:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance != TIM12)
{
return;
}
/* 停止状态: 关闭输出,等待按键 */
if (motor.state == MOTOR_STATE_STOPPED)
{
/* 确保 EN 处于禁用状态, LED 熄灭 */
HAL_GPIO_WritePin(En_GPIO_Port, En_Pin, GPIO_PIN_SET);
BSP_LED_Off(LED_GREEN);
motor.phase = MOTOR_PHASE_READY;
motor.acc_us = 0;
motor.pulse_ticks = 0;
motor.settling_ticks = 0;
motor.dir_change_pending = 0;
motor.steps_in_dir = 0;
return;
}
/* 运行状态: 使能电机, 点亮 LED */
HAL_GPIO_WritePin(En_GPIO_Port, En_Pin, GPIO_PIN_RESET);
BSP_LED_On(LED_GREEN);
switch (motor.phase)
{
case MOTOR_PHASE_READY:
{
/* 累计时间,达到设定的步进周期则发出一个上升沿 */
motor.acc_us += 1000U;
if (motor.acc_us >= motor.period_us)
{
motor.acc_us -= motor.period_us;
/* 如需换向: 此刻 STEP 必然为低,安全翻转 DIR */
if (motor.dir_change_pending != 0U)
{
motor.dir_change_pending = 0U;
Motor_SetDirection((motor.dir == MOTOR_DIR_CW) ? MOTOR_DIR_CCW : MOTOR_DIR_CW);
motor.steps_in_dir = 0;
motor.settling_ticks = MOTOR_DIR_SETUP_MS;
motor.phase = MOTOR_PHASE_SETTLING;
break;
}
HAL_GPIO_WritePin(step_GPIO_Port, step_Pin, GPIO_PIN_SET);
motor.pulse_ticks = 0;
motor.phase = MOTOR_PHASE_HIGH;
}
break;
}
case MOTOR_PHASE_HIGH:
{
/* STEP 上升沿之后保持 MOTOR_PULSE_HIGH_MS ms 高电平,
下一次进入本分支时拉低,产生下降沿 */
motor.pulse_ticks++;
if (motor.pulse_ticks >= MOTOR_PULSE_HIGH_MS)
{
HAL_GPIO_WritePin(step_GPIO_Port, step_Pin, GPIO_PIN_RESET);
motor.steps_in_dir++;
if (motor.steps_in_dir >= MOTOR_STEPS_PER_DIR)
{
/* 已走完单方向步数,标记下一次 READY 相位进行换向 */
motor.dir_change_pending = 1U;
}
motor.phase = MOTOR_PHASE_READY;
motor.pulse_ticks = 0;
}
break;
}
case MOTOR_PHASE_SETTLING:
{
/* 方向切换后等待 DIR 建立,期间不输出 STEP 脉冲 */
if (motor.settling_ticks > 0U)
{
motor.settling_ticks--;
}
else
{
motor.phase = MOTOR_PHASE_READY;
motor.acc_us = 0; /* 清零累加器,平稳启动新方向的第一步 */
}
break;
}
default:
motor.phase = MOTOR_PHASE_READY;
break;
}
}效果展示:【静音步进电机控制实践】 https://www.bilibili.com/video/BV1uE7W6FEXo/?share_source=copy_web&vd_source=2176e73645ba9710d1c29e12a1f03ada
我要赚赏金
