1. 硬件接线
计数时钟源:内部,APB1 Timer Clock = 250 MHz
预分频 PSC = 249 → 计数节拍 1 MHz(1 tick = 1 µs)
自动重载 ARR = 199 → 周期 200 µs,频率 = 1 MHz / 200 = 5 kHz
比较寄存器 CCR = 99 → 50% 占空比
2. 已实现的功能
上电默认停机:电机初始化后 EN 高、PWM 不输出脉冲,方向为 CW。
板载按键切换启停:按一次 B1 → 启动(EN 拉低、PC8 输出 5 kHz 方波);再按一次 → 停止(先关 PWM 再拉高 EN)。
LED 按键反馈:每次按下按键,绿灯短暂闪一下,给用户肉眼确认按键被识别。
方向切换(DIR 引脚已经接好,但没有触发手段)
调速(当前频率固定为 5 kHz)
限位/原点开关处理
串口指令、上位机协议
3. 软件架构
工程在 CubeMX 自动生成的骨架上,新增了一个独立的 motor 驱动模块,按钮中断作为唯一的外部事件源:
+---------------------------+ | main.c | | - HAL_Init / 时钟 / 外设 | | - motor_init(&motor_cfg) | | - BSP_PB_Init(BUTTON_EXTI)| | - while(1) 空闲 | +-------------+-------------+ | EXTI13 (上升沿) v +---------------------------+ | BSP_PB_Callback (用户) | | -> motor_toggle() | +-------------+-------------+ v +---------------------------+ | motor 模块 | | motor_init / start / | | stop / toggle / set_dir | | / set_freq | +-------------+-------------+ | GPIO + TIM3 PWM v TMC2209 SILENTSTEPSTICK -> 步进电机 -> 滑轨
模块之间的耦合只有函数调用,没有任何全局变量污染 main()。
4. motor 模块设计要点
驱动模块没有写死任何引脚号——所有硬件相关的参数都通过 motor_cfg_t 在 main.c 里组装好后传给 motor_init():
static const motor_cfg_t motor_cfg = {
.en_port = MotorEN_GPIO_Port, // GPIOB
.en_pin = MotorEN_Pin, // GPIO_PIN_15
.dir_port = MotorDir_GPIO_Port, // GPIOB
.dir_pin = MotorDir_Pin, // GPIO_PIN_14
.tim = &htim3,
.tim_channel = TIM_CHANNEL_3,
.default_freq_hz = MOTOR_DEFAULT_HZ, // 5000
};
motor_init(&motor_cfg);带来的好处是:换一块板、换一个定时器通道、或者把电机换成另一台,只需要改 motor_cfg,模块本身不动。
单例 + 公开结构
模块内只存在一个全局实例 motor_t motor,状态字段(频率、方向、运行中)直接可读:
typedef struct {
const motor_cfg_t *cfg;
uint32_t freq_hz;
uint8_t dir; // MOTOR_DIR_CW / MOTOR_DIR_CCW
bool running;
} motor_t;
extern motor_t motor;简单的查询通过头文件里的 static inline 直接读字段,不需要单独的 GetXXX() 函数——比上一版 Stepper_GetInfo() 之类的访问层薄一些。
启停时序
启动(motor_start()):把当前频率对应的 CCR 写进比较寄存器(保证第一帧就是 50% 占空比)
拉低 EN(TMC2209 使能)
HAL_TIM_PWM_Start 让 TIM3_CH3 开始输出
把 motor.running = true
HAL_TIM_PWM_Stop 关 OC 输出
HAL_TIM_Base_Stop 关计数器(避免残留触发)
清零计数器
拉高 EN(释放驱动器)
motor.running = false
motor_init() 里额外给 TIM3 打开了 OSSI 位——通道关闭时输出会被强制拉低而不是高阻,避免 PC8 浮空被 TMC2209 误识别为脉冲。
改频率
调用 motor_set_freq(hz) 时,如果电机正在转,会先 HAL_TIM_PWM_Stop 把输出稳住,再写 ARR + CCR + 清计数器,最后再 HAL_TIM_PWM_Start。换频过程中 STEP 线不会产生毛刺脉冲。
5. 按键回调路径
正确的用户入口是 BSP 提供的弱符号 BSP_PB_Callback。 触发链:PC13 上升沿 │ ▼ EXTI13_IRQHandler (Core/Src/stm32h5xx_it.c) │ ▼ BSP_PB_IRQHandler(BUTTON_USER) │ ▼ HAL_EXTI_IRQHandler(&hpb_exti[Button]) │ ├── 读 RPR1 -> 命中上升沿 │ └── 调用 BSP 自己注册的 RisingCallback ▼ BUTTON_USER_EXTI_Callback → BSP_PB_Callback(BUTTON_USER) │ ▼ 用户实现的 BSP_PB_Callback → motor_toggle()
main.c 里把 motor_toggle() + LED 闪烁放在回调里就够了。while(1) 主循环什么都不用做——PWM 由定时器硬件持续产生,按键由中断处理,两者天然解耦。
6. 构建配置
CMakeLists.txt 在 target_sources 里加了 Core/Src/motor.c,并把 Core/Inc 加入 include 路径。Debug 配置下编译产物:Memory region Used Size Region Size %age Used RAM: 1864 B 272 KB 0.67% FLASH: 27556 B 512 KB 5.26%
7. 效果展示

我要赚赏金
