STM32驱动28BYJ-48步进电机实战:接线、代码与踩坑记录 简介本资源是面向嵌入式初学者与STM32实践者的步进电机控制完整工程聚焦于使用STM32F103C8T6微控制器驱动常见5线四相步进电机28BYJ-48解决精确定位、启停控制与方向调节等典型运动控制问题适用于智能小车、3D打印平台、自动化演示装置等教学与原型开发场景。压缩包共261个文件含114个头文件.h定义外设与电机接口、69个源文件.c实现HAL库驱动、定时器PWM调速、ULN2003电平转换及正反转步序逻辑另有编译中间文件.o/.d/.crf与可执行镜像.hex/.axf整体7.04MB结构规范适配Keil MDK开发环境。已有2515人学习下载资源包含完整的STM32 HAL库工程框架覆盖I2C、UART、SPI、TIM、ADC等多外设初始化代码便于读者理解电机控制与系统协同逻辑并可快速迁移至同类电机或升级为细分驱动方案。 最近翻硬盘又看到那份《STM32控制步进电机28BYJ-48.zip》早几年第一次调28BYJ-48步进电机时我在接线、相位顺序和电源上花了不少冤枉时间。这次重新整理了一遍把工程里能用的代码、硬件接线和踩坑记录一次性写出来。大部分新手拿到STM32想做的第一件“会动”的事就是驱动一个步进电机。28BYJ-48恰好是最容易买到、资料最多、也最能逼你搞懂原理的型号它便宜、自带减速箱、5V就能跑配上ULN2003驱动板几乎成了各类DIY项目的标配。文章适合刚接触STM32、想让电机转起来但不知道从哪下手的读者也适合想弄明白“为什么这么接、为什么这么写”的人。搞清楚这个小电机后面再玩两相步进电机、伺服电机就顺了。1. 项目整体方案拆解为什么是“STM32 28BYJ-48 ULN2003”这套组合1.1 先认识一下28BYJ-4828BYJ-48是一只永磁式步进电机5根线、内部4个绕组、自带一级齿轮减速箱。型号里的“28”指的是电机外壳直径约28mmBYJ常被理解为永磁步进带减速48这个数字在不同资料里有不同说法不用太纠结。真正关心的是铭牌上那几个关键参数额定5V、步距角5.625°、减速比1/64。很多人第一次看到这组数字会懵我拆开讲一次。这里说的5.625°是内部电机转子每一步转过的角度是在“四相八拍”驱动方式下得到的。八拍就是按 A→AB→B→BC→C→CD→D→DA 的顺序给绕组通电每一步让转子走半个整步步距。因为有1/64的齿轮减速输出轴实际每一步只转 5.625 / 64 ≈ 0.0879°。算一下360 / 0.0879 ≈ 4096也就是让输出轴转一整圈需要精确给出4096个步进脉冲。这个数字后面写代码随时要用。说句实在话28BYJ-48的运动性能没有多强。减速之后输出转速低扭矩也有限空载稳定运行大概在每分钟15转上下带负载时还要再慢一点。它适合做什么智能窗帘、摄像头云台、自动浇花器的阀门、小型转台、广告展示架这些“要动但不用快”的场景是它的主场。相比之下3D打印机里常用的42步进电机是两相4线走的是A4988或DRV8825驱动方案和28BYJ-48不是一回事别混着用。1.2 ULN2003驱动板在这里面到底干了什么STM32的GPIO引脚输出高电平是3.3V最大输出电流通常就几毫安而28BYJ-48每个绕组的直流电阻只有几十欧姆5V供电时单相电流就要一百毫安左右四个绕组同时换相时尖峰更明显。直接用GPIO去拉轻则转不动重则把单片机引脚烧了。ULN2003模块的作用就是解决“小信号驱动大负载”的问题。它内部是7路达林顿晶体管阵列每一路都工作在集电极开路状态本质是一个反相器输入高电平时输出端导通到地输入低电平时输出端断开。电机绕组一端接5V电源正极另一端接ULN2003输出当STM32给对应输入引脚一个高电平输出端导通这条绕组回路就通电了。有人会问STM32的3.3V高电平过去ULN2003能不能识别可以。ULN2003的高电平输入门槛大概在2V左右3.3V完全够。驱动板上的VCC和GND是用来给板子内部逻辑和电机公共端供电的注意需要5V不是3.3V。这个模块还有一个隐藏好处内部每个通道输出端都带有续流二极管电机绕组是感性负载断电瞬间会产生反向电动势没有续流二极管很容易打坏驱动管ULN2003把这些都集成好了这也是它适合驱动这种小步进电机的原因。1.3 为什么不是L298N、不是A4988很多新手手边可能会翻出一块L298N模块想着“反正都是电机驱动应该能用”。L298N确实能驱动步进电机但它是为有刷直流电机设计的双H桥芯片体积大、压降大、还需要额外的逻辑电源驱动28BYJ-48属于大材小用。最尴尬的是L298N供电电压高一些效果才好而28BYJ-48常见的5V版本压降一扣实际加在绕组上的电压不足扭矩反而更差。A4988和DRV8825这类模块是给两相4线混合式步进电机设计的它们内部有微步细分、电流调节、STEP/DIR控制接口概念和5线4相永磁电机完全不匹配。28BYJ-48没有A相B相这种差分结构它是4个绕组共用一个公共端硬接A4988很容易出问题。ULN2003方案最大的优势就是“匹配”电机买回来自带驱动板五根线一插就完事。控制方式也直观4个GPIO按顺序换相即可不需要PWM不需要电流调节非常适合学习和做小型项目。2. 硬件连接接线、电源、共地一个都不能少2.1 常见引脚分配与完整接线表我这次用的主控是经典的STM32F103C8T6蓝色开发板也就是大家口中的“STM32最小系统板”。引脚分配上故意避开了一些特殊功能引脚选了PB0、PB1、PB10、PB11这四个普通IO口来连接ULN2003的IN1~IN4。这样做的原因是STM32F103的PB3、PB4、PA15默认作为JTAG调试引脚使用如果新手对这些引脚不熟悉配置不好会得到一个“不受控”的输出后面容易怀疑人生。ULN2003驱动板一端是4个输入端子和GND/VCC端子另一端是5个电机接线柱。正常接线表如下驱动板端子连接目标IN1STM32 PB0IN2STM32 PB1IN3STM32 PB10IN4STM32 PB11VCC外部5V电源正极GNDSTM32的GND必须共地电机端子28BYJ-48排线插上公共端对公共端28BYJ-48的排线通常是5根常见颜色是红、蓝、粉、黄、橙红色是公共端接驱动板的电源正极位置其余四色按丝印顺序插入。这个顺序在不同批次电机上可能存在差异所以我把代码引脚做成了数组映射如果转不动或方向反了直接调换数组里的引脚顺序就能解决不用重新接线。2.2 5V电源和“共地”问题这个坑我必须单独拎出来说。很多新手第一版接线喜欢直接从STM32开发板的3.3V引脚给ULN2003模块供电结果电机要么没劲要么转动几下之后单片机就整体复位了。原因是开发板上的稳压芯片一般只能输出几百毫安28BYJ-48启动和换相时瞬时电流很容易超过这个值电压一跌主控先崩了。28BYJ-48在5V供电下单相绕组电流大约100mA四相轮流通电时总电流可达三四百毫安建议准备一个能输出500mA以上、最好1A的独立5V电源。可以用USB电源、手机充电器改造的电源模块或者降压模块从更高电压降到5V。电源和STM32开发板之间一定要共地也就是把5V电源的GND和STM32的GND接到一起。如果不共地GPIO输出的3.3V高电平和ULN2003模块没有共同的参考电位信号乱飞电机可能完全不动或者随机乱跳。2.3 3.3V的STM32能不能直接驱动能但要注意配置。GPIO要设置成推挽输出模式GPIO_MODE_OUTPUT_PP而不是开漏模式。开漏模式下高电平是靠外部上拉电阻实现的如果没有上拉引脚输出高电平时会变成高阻态ULN2003输入拉不起来电机就罢工了。另外ULN2003的输入端和STM32之间要不要串电阻我的建议是驱动板输入端如果已经带了保护电阻就不需要再加没有的话串一个几百欧到1k欧的电阻也不会影响高电平识别还能避免接线失误带来的风险。不过实际项目中大多数廉价ULN2003模块输入端已经有串联电阻直接把杜邦线怼上去就能工作。3. 核心代码实现从点亮一个IO到稳定转动3.1 相位表和GPIO的映射关系步进电机控制的核心就是“按顺序给不同绕组通电”。我们要维护一张相位表表中每一行表示某一时刻四个GPIO的电平状态。以八拍半步方式为例static const uint8_t phase_table[8][4] { {1, 0, 0, 0}, // A相导通 {1, 1, 0, 0}, // A、B相导通 {0, 1, 0, 0}, // B相导通 {0, 1, 1, 0}, // B、C相导通 {0, 0, 1, 0}, // C相导通 {0, 0, 1, 1}, // C、D相导通 {0, 0, 0, 1}, // D相导通 {1, 0, 0, 1}, // D、A相导通 };第1行到第8行依次执行一遍输出轴正好走一个“内部步距角”也就是5.625°。执行4096次输出轴转一圈。为什么用八拍而不是直接四拍 A→B→C→D四拍也能转但每一步角度大一倍转子运动更生硬扭矩波动也大。八拍半步方式相当于在每两个整步之间插入了一个“两相同时导通”的状态让转子过渡更平滑噪音更小定位更细腻。这个细节很值得体会它是步进电机控制里非常基础的设计思想。GPIO的映射我用数组管理这样调整线序时不用改函数逻辑只需改数组内容#define M_IN1_PORT GPIOB #define M_IN1_PIN GPIO_PIN_0 #define M_IN2_PORT GPIOB #define M_IN2_PIN GPIO_PIN_1 #define M_IN3_PORT GPIOB #define M_IN3_PIN GPIO_PIN_10 #define M_IN4_PORT GPIOB #define M_IN4_PIN GPIO_PIN_113.2 用定时器中断产生精确的步进节拍控制步进电机最关键的是“每一拍之间的时间间隔要准确”。网上很多入门教程直接用延时函数while (1) { set_phase(phase_index); delay_ms(2); }这个写法在电机单独转、没有任何其他任务时勉强能用一旦你要同时处理按键、串口、显示延时函数就会卡住整个流程而且CPU大部分时间都浪费在空等上。更麻烦的是如果中断里不小心调用了带延时的函数系统直接卡死。我推荐用定时器中断实现定时器以固定周期触发中断每次中断里切换一拍主程序只负责下发“走多少步、正转还是反转”的命令。这样电机运转不再占用主循环后续想同时处理其他外设代码也好写得多。以STM32F103的TIM3为例芯片主频72MHz定时器挂在APB1总线上经过倍频后也是72MHz。设置预分频器PSC为71得到一个1MHz的计数时钟再设置自动重装值ARR为999定时器更新周期就是 1000 / 1000000 1ms。也就是说每1ms发生一次更新中断电机每1ms走一拍输出轴转速约14.65 RPM。3.3 支持速度、方向、步数的电机模块代码我把电机控制封装成了一个小模块包含初始化、启动、停止、周期回调四个部分。放在工程里直接调用即可。// motor.h #ifndef __MOTOR_H #define __MOTOR_H #include stm32f1xx_hal.h #define M_IN1_PORT GPIOB #define M_IN1_PIN GPIO_PIN_0 #define M_IN2_PORT GPIOB #define M_IN2_PIN GPIO_PIN_1 #define M_IN3_PORT GPIOB #define M_IN3_PIN GPIO_PIN_10 #define M_IN4_PORT GPIOB #define M_IN4_PIN GPIO_PIN_11 void Motor_Init(void); void Motor_Run(int32_t steps); void Motor_Stop(void); void Motor_PeriodElapsedCallback(void); #endif// motor.c #include motor.h static const uint8_t phase_table[8][4] { {1, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 1}, {0, 0, 0, 1}, {1, 0, 0, 1}, }; static volatile uint8_t phase_index 0; static volatile int32_t remain_steps 0; static volatile int8_t motor_dir 1; static void set_phase(uint8_t idx) { HAL_GPIO_WritePin(M_IN1_PORT, M_IN1_PIN, phase_table[idx][0] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN2_PORT, M_IN2_PIN, phase_table[idx][1] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN3_PORT, M_IN3_PIN, phase_table[idx][2] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN4_PORT, M_IN4_PIN, phase_table[idx][3] ? GPIO_PIN_SET : GPIO_PIN_RESET); } void Motor_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; TIM_HandleTypeDef htim3 {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin M_IN1_PIN | M_IN2_PIN | M_IN3_PIN | M_IN4_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance TIM3; htim3.Init.Prescaler 71; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 999; htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim3); HAL_TIM_Base_Start_IT(htim3); HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0); HAL_NVIC_EnableIRQ(TIM3_IRQn); } void Motor_Run(int32_t steps) { if (steps 0) return; motor_dir (steps 0) ? 1 : -1; remain_steps (steps 0) ? steps : -steps; } void Motor_Stop(void) { remain_steps 0; HAL_GPIO_WritePin(M_IN1_PORT, M_IN1_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN2_PORT, M_IN2_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN3_PORT, M_IN3_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(M_IN4_PORT, M_IN4_PIN, GPIO_PIN_RESET); } void Motor_PeriodElapsedCallback(void) { if (remain_steps 0) { return; } if (motor_dir 0) { phase_index (phase_index 1) 0x07; } else { phase_index (phase_index 7) 0x07; } set_phase(phase_index); remain_steps--; if (remain_steps 0) { HAL_GPIO_WritePin(M_IN1_PORT, M_IN1_PIN, GPIO_PIN_RESET); HAL_GPIO_Write p a hrefhttps://download.csdn.net/download/weixin_43469377/37960535 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

相关新闻

最新新闻

视频点播实战:基于Vue3+HLS.js+Node.js的演唱会点播系统

视频点播实战:基于Vue3+HLS.js+Node.js的演唱会点播系统

经典演唱会的长期点播,看起来只是把一个视频文件放到线上。实际上,当用户反复点击播放、拖动进度条、在不同网络环境切换清晰度时,背后涉及视频编码、切片、分发、播放器兼容和内容保护等一系列问题。以《Beyond 1991 生命接触演唱会》这类经…

2026/8/31 14:50:18
基于PSO粒子群算法的PID参数自动整定:MATLAB/Simulink仿真实践

基于PSO粒子群算法的PID参数自动整定:MATLAB/Simulink仿真实践

简介:本资源是一套面向自动化控制与智能优化初学者的MATLAB实践教学包,聚焦PID控制器参数自动整定这一工程核心问题,特别适用于课程设计、毕业设计及控制算法入门学习者。资源通过Simulink搭建典型被控对象闭环仿真模型,结合MATLA…

2026/8/31 14:50:18
软考必考点:计算机和鼠标到底是啥关系?一文搞懂UML类图四大关系!

软考必考点:计算机和鼠标到底是啥关系?一文搞懂UML类图四大关系!

文章目录💡 软考必考点:计算机和鼠标到底是啥关系?一文搞懂UML类图四大关系!🎯 核心判定法则:一句话口诀1. 泛化/继承关系 (Generalization)2. 依赖关系 (Dependency)3. 关联关系 (Association) —— 最宽泛…

2026/8/31 14:50:18
量化因子库收官:从数据流到可复用因子库的工程化实践

量化因子库收官:从数据流到可复用因子库的工程化实践

量化金融里最难的不是把一个因子算出来,而是让因子从原始行情数据一路走到因子库之后,还能被信任、被复用、被持续更新。这篇是“365天量化金融”因子阶段的收官内容,核心就两个字:数据流。很多人做量化做了几个月,因子…

2026/8/31 14:50:18
从零吃透 Modbus 通信|第 6 章:线圈功能码 05/0F 实现  Modbus‑TCP 基础入门

从零吃透 Modbus 通信|第 6 章:线圈功能码 05/0F 实现 Modbus‑TCP 基础入门

博客定位:扩展 Modbus 离散 IO 功能,完成 RTU 全部常用功能码;对比 RTU 与 TCP 差异,建立以太网 Modbus 认知,可直接发布 CSDN。前言前面章节我们完成保持寄存器(03/06/16)主机与从机、异常码、…

2026/8/31 14:50:18
开源工具将Pull Request变成动画架构图,让结构变化一目了然

开源工具将Pull Request变成动画架构图,让结构变化一目了然

接手一个新仓库,或者评审一个改动范围比较大的 PR 时,最消耗精力的往往不是读代码本身,而是先要在脑子里拼出“这次改动到底动了架构的哪一块”。文件多了之后,人的短期记忆根本装不下完整的调用链和依赖关系,评审就很…

2026/8/31 14:45:18