ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32 + 28BYJ-48步进电机:角度换算与OLED/串口联调实战

STM32 + 28BYJ-48步进电机:角度换算与OLED/串口联调实战 简介本资源是一套完整的STM32嵌入式控制实践项目源码面向单片机初学者与课程设计者解决步进电机精准控制、实时数据显示及串口调试协同开发等典型工程问题适用于自动化控制、智能硬件实训及毕业设计场景。压缩包含233个文件总大小6.33MB其中C源文件37个与头文件37个构成主控逻辑OLED.c等驱动模块实现屏幕显示stm32f10x_tim.c等标准外设库文件支撑定时器与GPIO配置.axf、.uvprojx等编译输出与工程文件保障Keil平台一键构建另有大量.o、.d、.crf中间文件体现完整编译链路。已有1547人学习下载。用户可直接烧录运行获得28BYJ-48电机角度实时刷新的OLED界面、同步发送至串口调试助手的ASCII格式数据流以及基于ULN2003驱动的稳定四相八拍控制逻辑代码结构清晰、注释完备便于理解步进电机细分驱动原理与多外设协同编程范式。 做 STM32 28BYJ-48 步进电机 ULN2003 驱动板 OLED 屏幕 串口调试助手这套组合时我最大的感受是硬件接线只是前菜角度换算和联调才是正餐。这个项目不复杂但很经典——单片机通过 ULN2003 驱动一个自带减速箱的步进电机按预设步数转动再把当前角度同时显示在 OLED 和串口调试助手上。做完你会发现它几乎把所有嵌入式基础外设都串了一遍GPIO、延时、定时器、UART、I2C、浮点打印。适合刚学完基本外设想搞点机电结合练习的人也适合想用低成本方案做转台、云台、阀门角度显示的朋友。1. 这套组合在做什么一个把“步数”翻译成“角度”的小系统1.1 项目最终能完成的事情先说清楚这套东西跑起来后的样子。上电后 OLED 第一行显示当前角度比如Angle: 0.00。你按下按键或者程序内部设定目标角度电机开始转OLED 上的数字跟着变同时串口调试助手里每隔一小段时间就打印一行比如Angle: 45.00 Angle: 45.00 Angle: 45.00这里有个关键点角度并不是靠传感器读回来的而是单片机根据自己发出的脉冲数换算出来的。28BYJ-48 每走一步输出轴理论上转过一个固定的角度单片机知道自己发了多少个步进脉冲自然知道现在大概在什么位置。这是一种典型的开环位置控制思路和闭环编码器方案最大的区别是它不感知真实位置只要电机没丢步、没堵转计算值就是准的。1.2 为什么值得搭一遍很多朋友学 STM32 都是点灯、按按键、跑个串口主从收发真正接触“机电结合”往往是从步进电机开始的。而这个项目把步进电机控制、OLED 显示、串口输出三件事揉在一起刚好覆盖了嵌入式开发最常用的一条链路逻辑计算 → 外设控制 → 人机交互 → 上位机观察。更实在的是这套硬件成本很低。一块 STM32F103C8T6 最小系统板加一个 28BYJ-48 电机加一个 ULN2003 驱动板加一块 0.96 寸 OLED几十块钱就能搞定。就算把电机烧了单独换一个也就几块钱拿来练手完全不心疼。1.3 这篇内容会讲什么我没有只丢一份源码完事而是把整个项目拆成五块电机和驱动的硬件关系、角度换算逻辑、CubeMX 工程配置、核心代码写法、以及我实测中踩过的坑。最后再聊几个扩展方向。代码部分用 HAL 库写标准库思路完全一样你顺着逻辑抄到自己的工程里就行。2. 28BYJ-48 与 ULN2003两个人组队才有意思2.1 28BYJ-48 到底是个什么电机28BYJ-48 是一个 5V 四相五线永磁减速步进电机。名字里的 28 指直径 28mmBYJ 是永磁减速的意思。它最大的特点是自带一个减速箱输出轴转速被降下来扭矩被放大所以特别适合做低速旋转负载。很多人第一次看它的参数会懵步距角写的是5.625°/64。这不是说一步 0.08789°而是指电机本体减速箱之前每一步转 5.625°再经过 1:64 的减速齿轮输出轴每一步实际转5.625 / 64 0.087890625°。所以让输出轴转一圈需要360 / 0.087890625 4096个半步脉冲。这个 4096 是后面所有角度计算的地基。电机有五根线颜色一般有红、蓝、粉、黄、橙其中红色通常接到 5V另外四根分别接四相线圈。不同品牌生产批次颜色可能不一样最好以驱动板丝印和实测为准不要看到颜色就死记硬背。2.2 ULN2003 驱动板存在的意义STM32 的 GPIO 能不能直接驱动这种电机答案是不能。GPIO 引脚输出电流太小带不动几十到几百毫安的线圈电流而且电机是感性负载断电瞬间会产生反向电动势直接打上去很容易让单片机端口损坏。ULN2003 本质上是一排达林顿晶体管阵列每个通道可以承受 500mA 左右的电流内部还集成了续流二极管。电机线圈断电时产生的反压会被续流二极管吸收保护前面的控制电路。这就是为什么大家总把 28BYJ-48 和 ULN2003 驱动板绑在一起电机是 5V 大电流负载单片机是 3.3V 小信号中间必须有个功率放大级的驱动板。常见 ULN2003 模块上有一个四针输入接口或者四根输入排针标着 IN1~IN4还有一个接线端子标着 A、B、C、D 和 VCC、GND。输入侧接到 STM32 的 GPIO输出侧接电机四根相线公共线接 5VVCC 接外部 5V 电源GND 和单片机共地。ULN2003 驱动板接口接法IN1STM32 GPIO如 PB0IN2STM32 GPIO如 PB1IN3STM32 GPIO如 PB2IN4STM32 GPIO如 PB3A/B/C/D电机四根相线VCC外部 5V 电源正极GND外部 5V 电源负极同时接 STM32 GND2.3 电机转起来的“通电顺序”步进电机不是一直通着电就能转它必须按特定顺序切换线圈通电状态。以半步模式为例常见的通电顺序是A AB B BC C CD D DA换成二进制位表示就是下面这张表节拍IN1(A)IN2(B)IN3(C)IN4(D)1100021100301004011050010600117000181001顺着走是正转倒着走就是反转。如果你代码里顺序不对电机常见的表现是嗡嗡响、震动、抖动但转不起来这时候多半是把某个节拍写错了或者线序接反了。3. 角度的真相减速比、半步和 4096 的换算3.1 减速比到底怎么影响角度很多入门例程直接写“一步 0.08789°”但没人告诉你这个数字是减速后的结果。如果只给电机本体发脉冲不经过减速箱转一圈只需要几十个步进。有了 1:64 减速箱后电机转子要转 64 圈输出轴才转一圈所以输出轴的角速度被压得很低但定位精度和扭矩都上来了。注意这个 1:64 是标称值实际齿轮比可能不是整数。有些资料测出来是 1:63.684误差不大但如果你长时间累计角度比如连续转几百圈误差会一点一点攒起来。做单圈或者小角度演示完全没问题做多圈精密定位就要考虑这个因素。3.2 半步、全步和 2048 的关系驱动步进电机有几种常见方式单相激励每次只通一相力矩小震动大基本不用。双相激励每次同时通两相叫全步转动档位大力矩相对好。单双相交替激励也就是上面的 8 拍半步转动更细腻震动更小。代码里如果用 8 拍半步输出轴转一圈需要 4096 个脉冲。如果只用 4 拍全步也就是 AB → BC → CD → DA 这种顺序输出轴转一圈只需要 2048 个脉冲每步对应 0.17578125°。驱动模式节拍数每转脉冲数每步角度半步840960.087890625°全步420480.17578125°很多教程和代码默认用半步所以源码里会看到4096这个常量。你拿到别人代码时先看它的节拍表是几拍别直接把全步和半步的常量混用否则角度会差一倍。3.3 角度换算公式与取值范围判断假设用一个有符号 32 位整数stepCount记录累计步数每走一步半步就stepCount或stepCount--那么当前角度可以算成float angle ((float)stepCount * 360.0f) / 4096.0f;这个公式很好理解步数占总步数4096 步一圈的比例乘上 360° 就是当前角度。正转为正角度反转就是负角度。但实际用的时候有三个细节要注意第一stepCount不要用无符号数。因为反转时角度要往下减无符号数会在过零处出问题。第二角度可以一直累计。如果电机朝一个方向转了很多圈angle会超过 360°也可能小于 -360°。这不算错误在某些场景下你需要知道“总共转了几圈”所以内部stepCount最好只增不减不要急着取模。显示的时候如果不想看到720°这种数字再做一个int angleWrap (int)angle % 360的包装显示函数就好。第三如果追求极致精确可以不用浮点。用整数运算来输出带两位小数的角度int32_t stepCount 1234; int32_t angleX100 ((int64_t)stepCount * 36000) / 4096; // 0.01度原理是把角度放大 100 倍最后显示时再拆成整数和小数。用int64_t做中间变量是为了防止乘 36000 时溢出。比如stepCount累计到 10 万步乘 36000 就是 36 亿已经超过 32 位整数范围了。不想用 64 位也有办法先除后乘再加余数但这个写法在实际项目里已经够用。4. 接线与 CubeMX 工程配置少走半天的引脚规划表4.1 引脚分配建议引脚选择没有绝对标准但有个原则不要和下载调试口、跟系统启动相关的引脚冲突。比如 STM32F103C8T6 的 PA13/PA14 是 SWDIO/SWCLK尽量别占用否则每次下载固件都要去改配置。下面是我常用的分配功能引脚说明电机 IN1PB0GPIO 输出推挽电机 IN2PB1GPIO 输出推挽电机 IN3PB2GPIO 输出推挽电机 IN4PB3GPIO 输出推挽OLED SCLPB6I2C1 时钟OLED SDAPB7I2C1 数据串口 TXPA9USART1 发送串口 RXPA10USART1 接收PB0 到 PB3 这组引脚在最小系统板上很容易引出来和 ST-Link 的 SWD 也不冲突。OLED 用硬件 I2C1省得用手动模拟 I2C 在那翻转电平。4.2 电源和共地是重灾区这个项目里最容易烧板子或者跑不起来的不是代码而是电源。28BYJ-48 电机工作电流不小单独一个电机启动时可能会抽走几百毫安如果直接用 USB 口那点电流又给 STM32 又给电机电压很容易被拉垮然后出现 OLED 花屏、串口乱码、电机抖动。正确做法是电机和单片机分开供电。单片机用 USB 或者 ST-Link 的 3.3V电机驱动板用外部 5V 电源比如一个 5V 1A 的充电器或者面包板电源模块。关键一定要把两边的地接在一起也就是外部电源的 GND 接到 STM32 的 GND这叫共地。没有共同参考地GPIO 输出的高电平和驱动板输入之间逻辑电平就乱套了。另外虽然 28BYJ-48 标称 5V但不同批次堵转电流差很多我见过空载 200mA、堵转 400mA 以上的。所以电源余量留大一倍比较稳。4.3 CubeMX 配置要点工程用 STM32CubeIDE 配 CubeMX 生成即可关键配置如下。RCC 里把 HSE 设为 Crystal/Ceramic Resonator这样系统时钟可以从外部 8MHz 晶振倍频到 72MHz。不接外部晶振用内部 HSI 也能跑但后面串口波特率可能偏得厉害稳定性也不如 HSE。系统时钟树里把 HCLK 设成 72MHz这个可以让后续串口波特率、定时器频率都规整。很多人串口乱码十有八九是这一步没配好系统实际跑在 8MHz 或者 64MHz但代码按 72MHz 设置波特率自然收发不对。SYS 里 Debug 选 Serial Wire这个必须做否则要用 ST-Link 在线调试时可能识别不到芯片。GPIOPB0 到 PB3 配成 GPIO_Output输出等级无所谓初始电平 Low。这里不要加外部上拉ULN2003 输入内部已经处理好。I2C1PB6 和 PB7 配成 I2C1模式选 I2C速度先选 Standard Mode 100kHz。等确认 OLED 能正常点亮再改成 Fast Mode 400kHz 不迟。I2C 引脚速度调太快反而容易出问题尤其是杜邦线一长信号完整性变差。USART1PA9 和 PA10波特率 1152008 位数据无校验1 位停止位这是市面上绝大多数串口调试助手的默认值。如果后面要做非阻塞电机调速再加一个定时器比如 TIM3内部时钟 72MHz分频后让更新中断频率在几百赫兹到几千赫兹之间可调。这一步不是必须先用HAL_Delay阻塞延时也能跑通但换成定时器中断后系统会灵活很多。4.4 硬件接线顺序建议我给新手一个经验顺序先把 STM32 和 ST-Link 调通能下载程序再把 OLED 接上跑个 I2C 扫描例程最后再接电机和驱动板。不要一次性全接完再上电出问题都不知道从哪查。接线时把电源线和信号线分开走尽量短尤其是 OLED 的 I2C 线长了容易波形变形。5. 代码拆解从 GPIO 电平到串口里的一行行角度5.1 步进电机状态机核心就一个函数电机控制的核心不是“延时”而是“状态切换”。我用一个静态变量保存当前节拍索引配合方向参数前进或后退。#define IN1_PORT GPIOB #define IN1_PIN GPIO_PIN_0 #define IN2_PORT GPIOB #define IN2_PIN GPIO_PIN_1 #define IN3_PORT GPIOB #define IN3_PIN GPIO_PIN_2 #define IN4_PORT GPIOB #define IN4_PIN GPIO_PIN_3 const uint8_t stepSeq[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 uint8_t stepIndex 0; volatile int32_t stepCount 0; // 累计步数有符号 void Motor_Step(uint8_t dir) { if (dir 1) { stepIndex (stepIndex 1) % 8; stepCount; } else { stepIndex (stepIndex 7) % 8; stepCount--; } HAL_GPIO_WritePin(IN1_PORT, IN1_PIN, stepSeq[stepIndex][0] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_PORT, IN2_PIN, stepSeq[stepIndex][1] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(IN3_PORT, IN3_PIN, stepSeq[stepIndex][2] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(IN4_PORT, IN4_PIN, stepSeq[stepIndex][3] ? GPIO_PIN_SET : GPIO_PIN_RESET); }stepCount用volatile修饰是因为它可能在中断里被修改主循环又要读它来做显示。这里方向dir为 1 表示正转0 表示反转反转时索引减一相当于把通电顺序倒过来走。5.2 按角度转动把角度需求换算成步数有了Motor_Step下一步就是“转指定角度”。比如要转 90°按半步模式就是90 / 360 * 4096 1024步。void Motor_SpinAngle(float angle, uint16_t stepDelayMs) { float targetSteps angle * 4096.0f / 360.0f; int32_t steps (int32_t)targetSteps; uint8_t dir (steps 0) ? 1 : 0; uint32_t count (steps 0) ? (uint32_t)steps : (uint32_t)(-steps); for (uint32_t i 0; i count; i) { Motor_Step(dir); HAL_Delay(stepDelayMs); } }这个函数很简单但有两个实际问题。第一HAL_Delay是阻塞延时函数返回前 CPU 一直在这里死等期间没法刷新 OLED也没法发串口数据。所以如果你需要边转边显示必须把步进功能放进定时器中断主循环负责刷新显示。下面这个函数可以改成中断里被调用。第二角度转步数时会遇到小数。比如angle 30°算出341.333步强制取整就变成 341 步误差不到 0.1°大多数场景可以接受。如果在意就把余数累计到下一个角度命令里。5.3 OLED 显示不用完整项目代码也能看懂思路OLED 用的是 SSD1306 驱动I2C 接口。0.96 寸的屏大部分 I2C 地址是0x3C少数是0x3D。你手里那块屏如果地址不对可以写个小扫描例程把总线上所有 I2C 设备地址扫出来几秒钟的事比猜强多了。显示角度的逻辑无非是三步float angle Get_Angle(); char buf[24]; sprintf(buf, Angle: %.2f, angle); OLED_Clear(); OLED_ShowString(0, 2, buf);OLED_Clear和OLED_ShowString是驱动库里的函数。如果你用的不是现成驱动自己写也不难光几个基础函数就能撑起全套显示OLED_WriteCmd、OLED_WriteData、OLED_SetPos、OLED_Clear、OLED_ShowChar、OLED_ShowString。我这里不贴整段 SSD1306 驱动网上类似 HAL 库版本很多。你只要把 I2C 读写打通能向0x3C发送命令字节和数据字节就已经完成 80% 了。注意在 CubeMX 里 I2C 引脚要配成开漏模式硬件上如果有 4.7k 上拉电阻最好很多 OLED 模块板上已经带不需要额外加。5.4 串口输出让“角度”进入调试助手把角度发到串口调试助手有两条路。一条是用printf重定向到 UART另一条是自己封装一个整数转换发送函数。printf方式最直观在工程里加一个重定向#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }然后在代码里直接写printf(Angle: %.2f deg\r\n, angle);用 STM32CubeIDE 的朋友注意默认工程可能不支持浮点printf。你需要给链接器加一个参数-u _printf_float否则在串口里只能看到Angle:后面是空的或者数字完全不显示。这个坑我帮别人排查过好几回都是卡在这里。不想用浮点printf的话用整数打印更稳int32_t angleX100 ((int64_t)stepCount * 36000) / 4096; uint8_t sign angleX100 0 ? - : ; uint32_t absVal angleX100 0 ? (uint32_t)(-angleX100) : (uint32_t)angleX100; printf(%c%ld.%02ld\r\n, sign, absVal / 100, absVal % 100);这样波特率不匹配时也不容易因为浮点占内存出问题而且很多轻量级串口屏、上位机解析起来更方便。5.5 主循环里面放什么如果你用阻塞方式驱动电机主循环大概是这样的int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, STM32 Stepper); HAL_Delay(1000); // 正转90度 Motor_SpinAngle(90.0f, 2); printf(Target 90, current: ); printf(Angle: %.2f deg\r\n, Get_Angle()); HAL_Delay( p a hrefhttps://download.csdn.net/download/m0_64562382/89241867 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
返回列表