STM32蓝牙控制步进电机:串口通讯与定时器脉冲例程实战 简介这份资源是一套面向嵌入式初学者与进阶开发者的STM32步进电机控制项目例程重点解决如何通过手机APP经蓝牙串口与STM32通讯、进而远程驱动步进电机的问题适用于智能家居、自动化设备等需要精确位置控制的场景。压缩包为rar格式共479个文件约71.95MB以c与h源码、o目标文件、crf交叉引用文件为主另含少量chm文档、uvproj工程文件及map、lst等编译产物覆盖主函数、蓝牙通信、步进电机驱动、定时器配置与命令解析等模块。目前已有838人学习下载。读者可从中获取完整的串口通讯协议栈实现、定时器PWM脉冲生成与中断控制思路、电机正反转与调速命令解析逻辑以及可直接编译的Keil工程结构便于快速上手STM32外设配置与无线控制方案。1. 手机蓝牙控步进电机这套 STM32 串口通讯例程到底能跑通什么手上有个 42 步进电机、一块 STM32 最小系统板、一个 HC-05 蓝牙模块想用手机 APP 发指令让电机正反转、调速、定步数——这个场景在工控 demo、小型自动化装置、教学实验里反复出现。但真正动手时卡人的往往不是电机本身而是三件事串口中断怎么收不定长指令、定时器怎么输出稳定脉冲、手机端和 MCU 端的协议怎么对齐。这套例程就是冲着这三个问题来的它把步进电机驱动、串口蓝牙通讯、定时器脉冲生成揉在一个工程里手机 APP 通过蓝牙串口发字符串指令STM32 解析后控制电机动作。适合刚接触 STM32 外设联动的新手照着复现也适合熟手拿去当协议框架改。下面按“资源是什么 → 怎么用 → 坑在哪”推一遍。2. 拆开工程看骨架定时器脉冲、串口中断、电机驱动怎么串起来2.1 为什么用定时器比较输出而不是软件延时步进电机要转得稳脉冲频率必须准。用HAL_Delay或for循环翻转 IO 的写法一旦串口来数据、中断一打断脉冲间隔就抖电机立刻发出“咔咔”异响甚至丢步。常见做法是用一个通用定时器比如 TIM2配成 PWM 输出模式或者用定时器更新中断里翻转 IO。前者硬件自动翻转CPU 几乎不参与后者灵活但中断频繁时占 CPU。这套例程走的是定时器中断翻转 IO 的路子好处是脉冲数可控、方便计步缺点是高频时中断压力大。关键参数就两个预分频器PSC和自动重装载值ARR。假设系统时钟 72MHz想要 1kHz 脉冲// TIM2 初始化72MHz 时钟目标 1kHz 更新中断 htim2.Instance TIM2; htim2.Init.Prescaler 72 - 1; // 72 分频 - 1MHz 计数频率 htim2.Init.Period 1000 - 1; // 1000 个计数 - 1kHz 中断 htim2.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_Base_Init(htim2); HAL_TIM_Base_Start_IT(htim2); // 开启更新中断PSC71把 72MHz 降到 1MHzARR999让计数器每 1000 个数溢出一次溢出频率就是 1kHz。改速度就是改ARRARR越小频率越高、电机越快。注意ARR不能小于脉冲宽度要求否则驱动器来不及响应。中断回调里翻转脉冲引脚并累加步数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 脉冲引脚翻转 if (step_count 0) { step_count--; if (step_count 0) { HAL_TIM_Base_Stop_IT(htim2); // 步数走完停脉冲 } } } }step_count是剩余步数由串口指令写入。走完自动停定时器避免电机一直转。方向引脚单独用普通 GPIO 控制换向时先停脉冲再改方向否则容易丢步。2.2 串口中断收不定长指令别用阻塞接收手机 APP 发过来的指令长度不固定比如SPD:500、MOV:200、DIR:CW用HAL_UART_Receive阻塞收必须知道长度不现实。正确姿势是开接收中断每来一个字节进一次回调攒够一帧再解析。常见做法是配一个接收缓冲区和索引uint8_t rx_buf[64]; uint8_t rx_idx 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint8_t ch rx_buf[rx_idx]; if (ch \n || rx_idx sizeof(rx_buf) - 1) { rx_buf[rx_idx] \0; parse_command((char *)rx_buf); // 解析整帧 rx_idx 0; } else { rx_idx; } HAL_UART_Receive_IT(huart1, rx_buf[rx_idx], 1); // 重新挂接收 } }这里用换行符\n作为帧结束标志手机端每条指令末尾加\n。rx_idx是写指针收到结束符就把缓冲区当字符串解析然后清零。注意每次回调末尾必须重新调用HAL_UART_Receive_IT否则只收一个字节就停了——这是新手最常翻车的地方。缓冲区大小 64 够用指令别超长超了会截断。2.3 指令解析字符串比对还是状态机指令格式简单时strncmp比对前缀最直接void parse_command(char *cmd) { int val; if (strncmp(cmd, SPD:, 4) 0) { val atoi(cmd 4); if (val 0 val 10000) { __HAL_TIM_SET_AUTORELOAD(htim2, 1000000 / val - 1); // 改速度 } } else if (strncmp(cmd, MOV:, 4) 0) { step_count atoi(cmd 4); HAL_TIM_Base_Start_IT(htim2); } else if (strncmp(cmd, DIR:, 4) 0) { if (cmd[4] C) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); else HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); } }SPD改ARR实现调速MOV设步数并启动DIR改方向。atoi把字符串转整数注意它不报错非法输入返回 0所以外面加了范围判断。指令多了以后strncmp链会变长可以换成查表或简单状态机但 demo 阶段够用。手机端 APP 用现成的蓝牙串口助手即可发送框里敲MOV:200\n就能让电机走 200 步。3. 从零复现接线、配置、烧录、联调四步走3.1 硬件接线与供电边界接线不复杂但供电是重灾区。STM32 的 IO 是 3.3V 逻辑HC-05 模块一般吃 3.6~6V步进驱动器如 TB6600、A4988的信号端通常兼容 3.3V但逻辑电平要确认。典型接法模块引脚接到 STM32说明HC-05TXDUSART1_RX (PA10)模块发MCU 收HC-05RXDUSART1_TX (PA9)模块收MCU 发HC-05VCC/GND5V/GND供电独立或板载 5V驱动器PULPA1脉冲信号驱动器DIRPA2方向信号驱动器ENAPA3使能可悬空注意步进电机和驱动器必须单独供电别从 STM32 的 5V 引脚拉电流带电机否则一转动 MCU 就复位这个现象十有八九是供电塌陷。HC-05 的 RXD 如果模块是 5V 逻辑直接接 3.3V 的 PA9 可能识别不稳常见做法是加个电平转换或串 1k 电阻分压。TX 方向模块 3.3V 输出MCU 能直接收问题不大。3.2 CubeMX 配置要点用 CubeMX 生成工程能省掉时钟树和引脚初始化的麻烦。关键配置项系统时钟外部晶振 8MHzPLL 倍频到 72MHz。USART1异步模式波特率 9600和 HC-05 默认一致开启全局中断。TIM2内部时钟PSC 和 ARR 先随便填代码里会动态改开启更新中断。GPIOPA1、PA2、PA3 配成推挽输出初始低电平。NVICUSART1 和 TIM2 中断优先级都使能USART 优先级可以略高于 TIM。生成代码后在main里启动第一次接收HAL_UART_Receive_IT(huart1, rx_buf[0], 1);只调一次后续在回调里续挂。忘了这句串口永远收不到第一个字节。3.3 手机端联调与指令验证手机装个蓝牙串口 APP配对 HC-05默认密码 1234 或 0000连上后打开发送框。先发DIR:CW\n用万用表量 PA2 应该变高再发MOV:200\n电机走 200 步后停。如果电机不转但 PA1 有波形查驱动器使能ENA和细分设置如果 PA1 没波形查定时器中断有没有进——可以在回调里翻转一个调试 IO 用示波器看。调速验证发SPD:500\n再MOV:1000\n听电机声音是否变尖、转速变快。SPD值对应频率500 就是 500Hz。注意别在电机运行中猛改ARR容易失步稳妥做法是停脉冲再改。4. 避坑排查这五个现象我踩过不止一次4.1 电机只抖不转现象上电后电机发出嗡嗡声但不旋转。原因脉冲频率太高超出驱动器响应或者相序接错。解决先把SPD降到 200 以下试确认能转再往上加相序按驱动器说明书对 A A- B B- 逐一核对别凭颜色猜。4.2 串口收到乱码现象手机发指令MCU 解析出乱七八糟的值。原因波特率不匹配或者 HC-05 和 MCU 的 TX/RX 接反。解决确认两边都是 9600用示波器量 TX 线看波形周期TX/RX 交叉接模块 TX 接 MCU RX别接成 TX 对 TX。4.3 走几步就停或丢步现象MOV:1000只走了几百步就停。原因step_count是uint16_t溢出或者中断里被其他高优先级中断打断太久。解决把step_count改成uint32_t检查有没有其他中断里做长延时把串口解析挪到主循环做中断只负责收字节。4.4 一开电机 MCU 就复位现象电机启动瞬间单片机重启。原因电机和 MCU 共用电源启动电流把电压拉垮。解决电机驱动器单独供电共地即可电源输入端并个大电容470uF 以上缓冲。4.5 蓝牙连上但发指令没反应现象APP 显示已连接发数据 MCU 无动作。原因HC-05 处于 AT 模式没退出或者HAL_UART_Receive_IT没续挂。解决模块上电时如果 EN 拉高会进 AT 模式正常通讯要 EN 悬空或拉低检查回调里有没有漏掉重新挂接收的那句。5. 进阶把定步数改成梯形加减速顺便验证脉冲均匀性定速跑在低速没问题速度一高启动瞬间电机直接失步——因为静止到高速没有加速过程。工业上常用梯形加减速加速段频率线性上升匀速段保持减速段线性下降。实现思路是在定时器中断里动态改ARR每走一步根据当前步数查表或算频率。// 梯形加减速简化实现accel_steps 加速步数target_arr 目标 ARR void update_speed(uint32_t current_step, uint32_t total_steps) { uint32_t arr; if (current_step accel_steps) { // 加速段ARR 从大变小 arr start_arr - (start_arr - target_arr) * current_step / accel_steps; } else if (current_step total_steps - accel_steps) { // 减速段ARR 从小变大 uint32_t dec_step total_steps - current_step; arr start_arr - (start_arr - target_arr) * dec_step / accel_steps; } else { arr target_arr; // 匀速 } __HAL_TIM_SET_AUTORELOAD(htim2, arr); }start_arr对应启动频率比如 200Hztarget_arr对应目标频率accel_steps是加速所需步数一般取总步数的 1/5 到 1/3。每进一次中断调一次update_speedARR平滑变化电机就不会“咯噔”一下。注意ARR改太小会让中断频率过高72MHz 下ARR别低于 72否则中断来不及处理。验证脉冲均匀性有个土办法把 PA1 接到逻辑分析仪或带频率计的示波器发MOV:2000看频率曲线是不是梯形。没有仪器就用示波器看周期抖动匀速段周期应该稳定。我一般还会在加速段故意把accel_steps设成 1听电机是不是直接失步以此确认加减速逻辑真的在起作用。从那以后我每次调步进电机都强制先低速验证脉冲和方向再上加减速最后才联蓝牙——顺序反了排查起来就是一团乱麻。希望帮到你。本文还有配套的精品资源点击获取