ARTICLE DETAIL

资讯详情

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

基于STM32的步进电机角度控制:OLED显示与串口通信实现

基于STM32的步进电机角度控制:OLED显示与串口通信实现 简介本资源是一套完整的STM32嵌入式控制项目源码面向单片机初学者与电子设计爱好者解决步进电机精准控制、实时数据显示与串口调试协同开发的实际问题适用于智能云台、自动化定位装置、教学实验平台等场景。压缩包含233个文件总计6.33MB其中C源文件37个与头文件37个构成主控逻辑OLED.c等驱动模块实现屏幕显示stm32f10x_tim.c等标准外设库文件支撑定时器与串口通信编译输出文件axf、map、lst等及Keil工程配置uvprojx、uvoptx确保开箱即用。已有1547人学习下载提供从硬件连接STM32ULN200328BYJ-48OLED到软件实现角度计算、半步进算法、I²C显示、USART数据透传的全链路代码支持结构清晰、注释完整便于理解电机控制时序、外设配置逻辑与多任务数据同步机制。1. 这套项目到底能干什么设计思路与系统架构1.1 一个综合了外设控制、显示与通信的小系统上个月翻出一块吃灰很久的 STM32F103C8T6 最小系统板又顺手从柜子里找到了一个 28BYJ-48 步进电机和一块 ULN2003 驱动板再加一块 0.96 寸 OLED 屏幕。当时就想着与其继续让它们各自吃灰不如拼一个小项目。于是就有了这套代码用 STM32 控制 28BYJ-48 步进电机转动到指定角度OLED 实时显示当前角度同时把角度数据通过串口发送到串口调试助手。这个项目看着不大但把嵌入式开发里最常用的几个模块全部串起来了GPIO 输出控制、定时器延时、步进电机驱动时序、OLED 屏幕驱动、串口收发和协议解析。对刚入门 STM32 的朋友来说如果能把这套代码自己敲一遍、调一遍基本上外设操作算是真正上手了。对已经有点基础的开发者来说也可以借此整理一下自己做综合实验时的代码结构尤其是步进电机角度换算和串口数据帧设计这两块其实可以直接复用到后续更多项目里。我最终的成果形态很朴素串口调试助手里输入一个角度值比如90.5OLED 上立刻显示“目标角度:90.5”电机开始转转到位后 OLED 显示“当前角度:90.5”同时串口回传一行ANG:90.50。整个过程没有复杂的操作系统没有高端算法就是老老实实地把外设用好。但恰恰是这种“老实”写法能把很多隐患暴露出来。1.2 系统工作流程和各模块分工这套系统的工作流程可以从上电开始拆成四个阶段第一阶段是初始化。STM32 上电后先配置系统时钟再初始化 GPIO 引脚。电机四个相接到 PA0 到 PA3OLED 的 I2C 接到 PB6 和 PB7串口 TX 和 RX 接到 PA9 和 PA10。初始化完成后OLED 先显示一个欢迎界面然后进入待机状态。第二阶段是接收用户指令。用户通过串口调试助手发送角度数据数据以123.45或者-45.6这种格式传下来。STM32 在串口接收中断里把完整一帧数据收进缓冲区等收到结束符\n时解析函数就把这一串字符转成浮点数。第三阶段是执行电机转动。解析出的目标角度先与当前角度做差得到相对角度偏移。再根据 28BYJ-48 的减速比和步距角把偏移角换算成需要走的步数。然后通过一组定时器延时按照半步驱动时序逐拍输出电平让电机一步步转过去。第四阶段是结果反馈。电机停转后OLED 更新显示当前角度同时串口以固定格式回传当前角度值帮助用户在串口调试助手上确认执行结果。这里有个容易忽略的设计点角度值不是电机轴本身的位置而是通过“目标角度”和“已走步数”反推出来的。也就是说这是一个开环控制。电机有没有真的转到那个位置依赖的是步进电机“给一拍走一步”的基本特性只要不丢步误差就不会积累。2. 硬件选型与接线为什么非要用ULN20032.1 28BYJ-48电机的特性28BYJ-48 是一种非常常见的永磁减速步进电机额定电压常见的有 5V 和 12V 两个版本我这边用的是 5V 版。它的内部结构其实包含了两部分一部分是四相步进电机本体另一部分是 1/64 的塑料减速齿轮箱。我们看到的电机输出轴其实是经过减速齿轮后的输出所以它转一圈需要走的步数不是步进电机本体的 64 步而是 64 步乘以减速比再考虑驱动方式才能算出来。很多人第一次接触 28BYJ-48 时都会在参数换算上栽跟头。手册上写的步距角是5.625°/64这个写法容易让人误以为输出轴步距角就是 5.625° 除以 64。实际含义是步进电机本体在没有减速的情况下每步走 5.625°经过 1/64 减速后从输出轴看每一步对应的角度是5.625° / 64 ≈ 0.08789°。也就是说输出轴转一圈需要360° / 0.08789° ≈ 4096步。但要特别注意这个 4096 步是采用半步驱动时的结果。28BYJ-48 的四相电机有两种常见驱动方式整步驱动和半步驱动。整步驱动步距角是 5.625°输出轴转一圈需要 2048 步半步驱动步距角减半输出轴需要 4096 步。我最后选择的是半步驱动因为这样转动更平稳而且在低速场合不会出现明显的振动。2.2 接线与供电注意事项接线这块不复杂但特别容易因为线序搞错而翻车。28BYJ-48 引出的五根线常见颜色是蓝、粉、黄、橙、红。红色线是电源正极接到 ULN2003 驱动板的 端其余四根信号线按顺序接到驱动板的 IN1 到 IN4 上。驱动板输出端再对应接到 STM32 的 PA0 到 PA3。在连接驱动板电源时强烈建议把电机电源和 STM32 的电源分开。如果直接用 STM32 板上那个低压差稳压器去给电机供电电流很容易被拉垮导致复位或者显示乱码。我实际测试过电机转动时瞬时电流能到 200mA 以上24BYJ-48 启动瞬间电流还要更高单片机的 3.3V LDO 根本扛不住。最稳妥的做法是电机驱动板单独用 USB 5V 供电STM32 用另一根 USB 线供电两个电源地线连到一起。这里还有一个容易忽略的细节ULN2003 驱动板上的 IN1 至 IN4 引脚电平是 5V 兼容的但 STM32 的 GPIO 是 3.3V 电平。绝大多数情况下3.3V 信号直接推到 ULN2003 的输入端是可以被识别成高电平的不过为了保证可靠性还是建议看看板子是否有电平转换电路。我手头的驱动板实测直接连接没有问题如果你用的是其他型号最好先用万用表量一下输入端的阈值。OLED 屏幕的连接相对自由一点。0.96 寸 OLED 常见的是 I2C 接口只有 SCL、SDA、VCC、GND 四根线。我的 I2C 接线是 SCL 接 PB6SDA 接 PB7。OLED 的供电直接用 STM32 板上的 3.3V 或者 5V 都可以注意 OLED 模块上往往自带稳压如果屏幕背后印着 3.3V就接 3.3V。3. 软件工程搭建STM32外设初始化与OLED移植3.1 新建HAL库工程时的时钟和引脚配置写这套代码时我选择的是 STM32CubeMX 生成工程然后基于 HAL 库手写业务逻辑。原因很简单HAL 库的函数封装层比较厚尤其串口中断回调这类接口写起来直观对进阶学习也比较友好。当然如果你习惯标准库也没问题核心算法和驱动时序都是通用的。工程生成时时钟树配置成 72MHz。STM32F103C8T6 使用 HSE 8M 晶振PLL 倍频到 72MHz如果板上用的是 HSIC 内部时钟也可以把主频配到 64MHz只是串口波特率会有些许误差建议优先用外部晶振。GPIO 配置上我把 PA0 到 PA3 设置为推挽输出初始电平低速度设置为 Low。步进电机驱动频率通常只有几百赫兹没必要用 Very High 速度反而可能引入电磁干扰。OLED 的 PB6 和 PB7 要设置为开漏输出因为它们要模拟 I2C开漏配合上拉电阻才能输出正确的高电平。很多新手直接把 I2C 引脚设成推挽结果屏幕要么不亮要么显示稀烂。串口配置为 UART1波特率 115200数据位 8停止位 1无校验。接收部分启用全局中断这样可以在中断回调里接收一帧完整数据。启用一个基础定时器 TIM3用来提供步进电机的节拍延时。为什么不用HAL_Delay因为HAL_Delay基于 SysTick而 OLED 显示、串口输出等很多操作也依赖 SysTick如果在电机步进过程中频繁调用HAL_Delay一旦被中断打断就容易造成时序抖动导致电机抖动或者失控。用独立定时器做闭环延时代码的可移植性和稳定性都会好很多。3.2 OLED显示层的移植要点0.96 寸 OLED 的控制芯片是 SSD1306通常有 I2C、SPI 和并行三种接口方式。我使用的是 I2C 版本地址默认是 0x3C如果你的模块是 0x3A需要检查背面的电阻配置。初始化代码可以直接移植一些成熟开源工程的驱动但要注意两点一是 I2C 命令发送的起始条件、停止条件、 ACK 等等时序必须严格按照 SSD1306 数据手册来二是页面寻址模式下写数据前要先设置列地址和页地址否则会出现显示错位。我在移植时用到的初始化序列就是经典的四步关显示、设置显示时钟分频、设置多路复用比、设置段重映射和 COM 扫描方向。把这一串寄存器设置代码复制过来时不要为了省事省略前面几条延时部分 OLED 模块对初始化时序非常敏感上电后需要等待几十毫秒再发命令。显示层的功能我主要实现了三个函数清屏、显示字符串、显示整数或浮点数。实际项目中OLED 并不需要很复杂的界面我只显示两行第一行显示目标角度第二行显示当前角度。显示浮点数时需要特别注意格式化问题。HAL 库的默认printf重定向其实没有直接支持%f如果你用sprintf输出浮点数会发现打印出来是空的。解决方法有两个要么用自定义的int_part和dec_part分别取整要么把工程里的微库打开并加上-u_printf_float链接选项。我采用的办法是自定义浮点转字符串函数这样不依赖编译器选项跨平台复用也方便。4. 步进电机角度控制的核心算法4.1 步数、角度与减速比的换算角度控制的第一步是建立“角度”和“步数”的数学关系。前面已经说过28BYJ-48 半步驱动下输出轴转一圈需要 4096 步因此每一度对应的半步数是#define STEPS_PER_REV 4096.0f #define DEG_PER_STEP (360.0f / STEPS_PER_REV) // 约 0.08789°那么需要执行的步数就是float angle_diff target_angle - current_angle; int32_t target_steps (int32_t)(angle_diff / DEG_PER_STEP);这里要注意 C 语言浮点转整数的截断问题。如果角度差是0.05°理论上只需要0.57步但执行半步驱动时至少得走一步否则电机根本不会动。处理方式是把target_steps四舍五入而不是直接截断int32_t target_steps (int32_t)(angle_diff / DEG_PER_STEP 0.5f);对于负数这个公式同样成立因为0.5f在负数方向也可以完成四舍五入。实际验证后把目标角度设为 90°、180°、360° 时误差都在预期范围内。4.2 电机的驱动时序和单步执行28BYJ-48 是四相步进电机半步驱动时八个状态依次激励四个线圈每个状态切换转子转半个步距角。我定义的半步驱动表如下const uint8_t halfstep_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} };每执行一步就是把四路 GPIO 同时设置为数组中的一行值。为了正反转方便可以定义两个方向标志位void Motor_Step(int8_t direction) { static uint8_t step_index 0; if (direction 0) step_index (step_index 1) % 8; else step_index (step_index 7) % 8; HAL_GPIO_WritePin(MOTOR_IN1_GPIO_Port, MOTOR_IN1_Pin, halfstep_table[step_index][0]); HAL_GPIO_WritePin(MOTOR_IN2_GPIO_Port, MOTOR_IN2_Pin, halfstep_table[step_index][1]); HAL_GPIO_WritePin(MOTOR_IN3_GPIO_Port, MOTOR_IN3_Pin, halfstep_table[step_index][2]); HAL_GPIO_WritePin(MOTOR_IN4_GPIO_Port, MOTOR_IN4_Pin, halfstep_table[step_index][3]); }每两步之间的延时决定了电机转速。我在代码里把延时设置为 1ms也就是每秒走 1000 个半步输出轴转速大约为1000 / 4096 * 60 ≈ 14.6 rpm。这个速度听起来很慢但 28BYJ-48 本身是减速电机适合做位置控制不适合高速旋转。如果你发现电机在高速时丢步检查一下是不是延时太短、电压不足或者负载太大。4.3 串口指令解析与角度反馈数据打包串口协议我用了最简单的文本协议方便在串口调试助手里直接看效果。用户发送一行字符串格式为[角度]\n比如123.45\n。STM32 在HAL_UART_RxCpltCallback中不断接收单个字节直到收到\n则认为一帧结束。解析代码采用strtof函数可以直接把字符串转成浮点数char received_frame[32]; uint8_t frame_len 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint8_t ch; HAL_UART_Receive_IT(huart1, ch, 1); if (ch \n || frame_len 31) { received_frame[frame_len] \0; target_angle strtof(received_frame, NULL); frame_len 0; } else { received_frame[frame_len] ch; } } }反馈的角度数据也做成文本帧例如ANG:90.50\r\n。在串口调试助手上每执行一次命令日志区就会显示一条ANG:90.50后续想接上位机做曲线只需要加一个简单的解析脚本即可。5. 串口调试助手端的实际效果与数据解读5.1 数据帧格式和发送逻辑很多新人写串口发送喜欢在循环里狂发HAL_UART_Transmit。这样确实简单但会造成上位机收到大量没意义的重复数据。我的发送逻辑是“状态变化才发送”即在电机执行完目标角度后发送一次当前角度。发送内容可以灵活调整。如果你只发纯数字比如90.50\n上位机处理更方便如果你希望日志里有语义可以加协议头ANG:。但要注意协议头和数字之间不要加多余的空格否则有些上位机脚本解析时会有歧义。实际发送代码如下char uart_tx_buf[32]; sprintf(uart_tx_buf, ANG:%.2f\r\n, current_angle); HAL_UART_Transmit(huart1, (uint8_t *)uart_tx_buf, strlen(uart_tx_buf), 100);这里HAL_UART_Transmit是阻塞发送。在 115200 波特率下一帧 20 字节的数据发送时间不到 2ms完全不会阻塞主循环。如果你后续要做大量数据连续发送建议改成 DMA 发送不然会拖慢 CPU。5.2 用串口调试助手验证电机精度串口调试助手我用的比较多的是 XCOM 和 SSCOM两者都能满足要求。连接好 USB 转串口后把波特率设为 115200数据位 8停止位 1无校验。发送区打开“发送新行”也就是在数据末尾加上\r\n否则 STM32 可能收不到帧结束符。在调试助手输入180并发送OLED 上应立即显示目标角度180.0电机开始旋转。转完后串口回传ANG:180.00。如果你往卡槽里放一个小指针会发现指针大致停在 180° 方向。由于 28BYJ-48 是减速电机转动过程其实比较漫长。半步驱动转满一圈需要 4096 步每步 1ms转一圈需要 4 秒多。如果目标是 180°大约需要 2 秒。这个速度没法跟伺服电机比但用来做仪表盘指针、云台定位这类场景完全够用。角度精度的验证有个小技巧让电机连续走 10 次 45°也就是累计 450°看最终位置是不是停在初始位置加上 90° 的地方。如果每次都回到同一个位置说明步数计算和驱动时序没问题。开环控制下出现小误差不可怕真正可怕的是丢步那会导致角度越走越偏。6. 调试中踩过的一堆坑现象、原因与解决办法6.1 电机嗡嗡响但不转如果电机发出明显的嗡嗡声但没有转动输出第一反应是驱动时序顺序不对。28BYJ-48 的标称线序可能和驱动板的丝印不完全一致。我遇到过几次接线颜色看着是按顺序的但实际接进驱动板后四相激励顺序混乱导致电机只是原地颤抖。排查方法很简单把 PA0 到 PA3 任意两个引脚互换或者把其中一相的顺序反转逐个试。许多电机驱动项目里都有一句“至少要试三种线序组合”我实测确实如此。另一个常见原因是电源电压不够。如果供电线太细或者电源本身电流输出能力差电机启动瞬间电压被拉低驱动板逻辑乱掉电机也会只响不转。解决方法是换成足够粗的杜邦线并尽量使用 5V 2A 以上的电源适配器。6.2 OLED白屏或满屏雪花白屏通常是 I2C 初始化期间出问题。先确认模块地址对不对再确认 SCL 和 SDA 是否接反。如果接反了OLED 没有任何反应。满屏雪花通常是初始化序列中断。有些 OLED 上电后需要一段时间稳定如果初始化代码在系统上电后立刻执行可能会失败。最简单的解决办法是在 OLED 初始化前加一个 100ms 左右的延时让模块完成内部复位。另外如果你的 OLED 是 SPI 接口而代码里写的是 I2C那肯定点不亮。买模块的时候一定要看清楚0.96 寸 OLED 有 I2C、SPI、并行三种版本接口不一样驱动代码也完全不一样。6.3 串口收到的数据乱码、丢字节串口乱码第一检查波特率。用内部时钟时如果不校准115200 波特率可能出现较大误差建议直接看 STM32CubeMX 里计算出的实际波特率如果误差超过 2%乱码概率会很高。第二检查 GND 是否共地。USB 转串口和 STM32 板子不共地时电平参考点不同收到的基本全是乱码。丢字节、收一帧少几个字符多半是因为接收中断不够快或者帧内字符间隔太长。在处理回调时我通常把接收到的字符先存进一个简单的数组不做耗时的解析操作等帧结束再统一处理。这样即使 CPU 在刷 OLED也不会丢字节。6.4 角度误差越走越大步进电机开环控制下角度误差只来自丢步和启停冲击。如果每次执行完目标角度后发现误差逐渐变大说明电机在某个时刻丢了几步。最常见的原因是转速过快6ms 一步时可能问题不大一旦加速到 1ms 一步又没有加减速段电机惯性和负载可能导致转子跟不上磁场变化。解决办法是加入简单的梯形加减速。比如把整个执行过程分成三段前 20% 步数速度逐渐加快中间匀速后 20% 减速停止。对于 28BYJ-48 这种小电机加减速段可以用一组延时数值表实现不需要复杂的 S 曲线。另一个容易忽略的误差来源是执行方向切换时的回程间隙。28BYJ-48 内部是塑料齿轮齿轮之间存在空程。正转和反转切换时电机需要先消除空程才会带动输出轴。在要求精确位置到正负误差小于 0.5° 的场景建议在反转前先多走 3 到 5 步再反向走回来。7. 一点个人经验和扩展方向7.1 开环控制下如何把误差压到最低如果要让这套开环系统尽可能精准我的经验是把“角度换算”和“执行节奏”分开来看。角度换算只做一次保证目标步数和执行方向都正确执行节奏则通过分段延时表实现减少启停冲击。做完以上优化后我实测连续执行 20 次 90° 指令最终位置和理论位置误差基本在 1° 以内。对于四线步进电机来说这个精度已经符合绝大多数小型设备的需求。如果你追求更高精度就只能加编码器做闭环了比如在输出轴上装一个 AS5600 磁编码器STM32 读取编码器角度后做 PID 补偿。7.2 后续能往哪些方向升级这套系统本身就是一个很好的基础架子。你可以把串口协议换成更稳健的二进制帧格式比如0xAA 0x55 角度高字节 角度低字节 CRC这样上位机连 LabVIEW 或者 PyQt 界面时更方便。也可以把 OLED 换成更小的 0.91 寸屏幕或者改成多个页面显示更多参数。更实用的是把电机角度控制接入 Linux 上位机实现通过 Python 脚本给串口发送角度值STM32 执行并返回当前角度再配合摄像头做简单的视觉定位。我在后续扩展中就是这样做的上位机发一个激光雷达扫描到的目标角度STM32 控制电机转过去OLED 同时显示当前位置。整个代码结构几乎没有改动只是重新封装了串口协议。如果你手头也有类似的板子强烈建议跟着搭一遍。硬件成本加起来不到五十块但涉及的知识点几乎覆盖了 STM32 入门时需要掌握的绝大多数内容。把电机控制、OLED 显示、串口通信、定时器延时这几个模块吃透再去做四轴机械臂、摄像头云台、自动化小设备思路都会清晰很多。本文还有配套的精品资源点击获取
返回列表