第二阶段成果交付物:在第一阶段"按键启停"基础上,加入 方向控制(往复运动) 和 速度控制(UART 调速),并用 TIM12 提供 10 ms 时基来调度相位切换。
1. 任务要求 → 实现映射
2. 与第一阶段的差异
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 的累计误差。
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 浮点格式化字符串
整体仍处于个位数百分比的占用率,还有大量余量。
我要赚赏金
