【let's do|2026年第3期】步进电机的串口控制
本文介绍了 Let's Do|2026年第3期 活动,通过主控 NUCLEO-C562RE 开发板实现 28BYJ48 步进电机驱动和串口控制的项目设计,包括硬件连接、环境搭建、工程测试等流程。
项目介绍
NUCLEO-C562RE 开发板通过串口中断控制 28BYJ-48 步进电机旋转的角度、方向、速度的项目设计。

步进电机和驱动板介绍、硬件连接;
串口通信:工程创建、参数配置、IDE 导入、代码、编译运行等;
电机驱动:头文件和源文件、驱动旋转测试;
角度控制:关键代码、串口中断、指令发送、自定义旋转;
28BYJ-48
28BYJ-48 是一款常见的低成本、小扭矩 5 线单极步进电机,可使用 ULN2003 控制器和单片机实现旋转控制,广泛用于打印机、扫描仪、摄像机云台、空调、家电、玩具、消费电子等领域。


参数
28BYJ-48 是一款适应性强的 5V DC步进电机,设计紧凑,适用于各种应用。
它具有四个相位,速度变化比为1/64,步距角为5.625°/64步,允许精确控制。
电机在100Hz频率下工作,在25°C时的直流电阻为50Ω ±7%。
它的空载牵引频率大于600Hz,空载脱离频率超过1000Hz,确保在不同场景下的可靠性。
28BYJ-48 在 120Hz 时的自定位转矩和牵引转矩均超过 34.3mN.m,提供强劲性能。
其摩擦转矩范围为600到1200 gf.cm,而拉入转矩为300 gf.cm。
| 参数值 | 描述 |
| 电机类型 | 单极 4 相永磁式步进电机(5线制) |
| 步距角 | 5.625°(64 步/圈),配合减速齿轮后 0.0879°(实际输出轴 4096 步/圈) |
| 减速比 | 1:64(内部齿轮组减速) |
| 额定电压 | 5V 或 12V DC |
| 相电流 | 约 100mA(每相) |
| 保持扭矩 | 约 0.1 N·m(输出轴,受减速齿轮影响) |
| 绕组电阻 | 约 50Ω/相 |
实际输出轴步距角为 5.625°/64 ≈ 0.0879°,转一圈理论上需要 64×64=4096 步,实际可能存在误差。
详见:Seeed Studio Wiki .
ULN2003
ULN2003 是一款常用的达林顿晶体管阵列芯片,专为驱动高电流负载(如继电器、步进电机、LED阵列等)设计。其作用是将 MCU 输出的弱电流信号转换为大电流输出,是控制 28BYJ-48 步进电机的核心驱动芯片。


原理图


详见:Seeed Studio Wiki .
硬件连接


由原理图可知,将驱动板的 4 个信号输入引脚分别与 STM32C562RET6 的 PA9、PC6、、PC9、PA7 相连;


信号引脚电平为 3.3V,电机驱动电压为 5V,因此需将开关拨至 VM 端;
| STM32C562RET6 | ULN2003 Module |
| Digital Pin 8 (PA9) | IN1 |
| Digital Pin 9 (PC6) | IN2 |
| Digital Pin 10 (PC9) | IN3 |
| Digital Pin 11 (PA7) | IN4 |
| GND | GND |
| 3V3 | VCC |
| 5V | VM |
串口通信
使用串口中断控制步进电机,因此需首先调通串口通信。
由原理图可知,板载 ST-LINK 虚拟串口 (VCP) 连接对应 USART2,引脚为 PA2 (TX) 和 PA3 (RX) ;


工程创建
打开 STM32CubeMX2 软件,从开发板新建工程;
点击左侧工具栏 Open Peripherals 按钮,展开 Connectivity ,使能 USART2 并配置为异步通信;


展开 System - GPIO ,将 USART2 对应引脚配置为 PA2 和 PA3,并使能全局中断;


进入 Pinout 标签页,将 ULN2003A 模块的信号引脚 PA9、PC6、、PC9、PA7 均设置为输出模式,初始低电平;


保存配置,生成 CMake工程;
详见:【let's_do|2026年第3期】介绍、环境搭建、工程测试 .
导入 IDE
打开 STM32CubeIDE 软件,依次选择 File - Import ,选择 STM32 CMake Project ,选择目标工程文件所在路径;


配置工具链,选择主控芯片型号 STM32C562RETx ;


待导入完成,点击工具栏 小锤子 按钮,编译工程,无报错。
代码
打开 StepMotor_cmake/main.c 文件,添加代码如下
#include "main.h"
#include <stdio.h>
#include <string.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define RX_TIMEOUT 10000U
#define TX_TIMEOUT 1000U
#define MAX_BUFFER_SIZE 128U
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t RxBuffer[MAX_BUFFER_SIZE];
/* Private functions prototype -----------------------------------------------*/
int __io_getchar(void) {
uint8_t ch = 0;
HAL_UART_Receive(mx_usart2_uart_gethandle(), (uint8_t *)&ch, 1, 10000);
return ch;
}
int __io_putchar(int ch) {
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)&ch, 1, 100);
return ch;
}
int main(void)
{
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
while (1) {
printf("Hello World!\r\n");
HAL_Delay(500);
}
}
} /* end main */保存代码。
效果
重新编译工程、点击工具栏 Run 按钮,配置调试器为 ST-LINK ,运行程序;


运行串口调试软件,配置波特率等参数,打开串口;
连续接收开发板输出的字符串;


电机驱动
在 IDE 项目中新建步进电机驱动的头文件 stepmotor.h 和源文件 stepmotor.c 。
stepmotor.h
#ifndef __STEPMOTOR_H_
#define __STEPMOTOR_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
/* 步进电机引脚定义(PA9, PC6, PC9, PA7)*/
#define STEPPER_PIN0_PORT HAL_GPIOA
#define STEPPER_PIN0_PIN HAL_GPIO_PIN_9
#define STEPPER_PIN1_PORT HAL_GPIOC
#define STEPPER_PIN1_PIN HAL_GPIO_PIN_6
#define STEPPER_PIN2_PORT HAL_GPIOC
#define STEPPER_PIN2_PIN HAL_GPIO_PIN_9
#define STEPPER_PIN3_PORT HAL_GPIOA
#define STEPPER_PIN3_PIN HAL_GPIO_PIN_7
/* 方向定义 (正数=顺时针,负数=逆时针)*/
#define STEPPER_CW 1 /* 顺时针 */
#define STEPPER_CCW -1 /* 逆时针 */
/* 公共函数 */
void stepper_init(void);
void stepper_step(int32_t steps, uint16_t delay_ms);
void stepper_rotate(int32_t angle, uint16_t delay_ms);
void stepper_stop(void);
void stepper_test(void); /* 测试 */
#ifdef __cplusplus
}
#endif
#endif /* C562RE_STEPMOTOR_CMAKE_STEPMOTOR_H_ */保存代码。
stepmotor.c
#include "stepmotor.h"
/* 四相八拍励磁序列(8 个状态)
位序: [PA9, PC6, PC9, PA7] 对应 [A, B, C, D] 相 */
static const uint8_t step_sequence[8] = {
0b0001, /* 1: A */
0b0011, /* 2: A+B */
0b0010, /* 3: B */
0b0110, /* 4: B+C */
0b0100, /* 5: C */
0b1100, /* 6: C+D */
0b1000, /* 7: D */
0b1001 /* 8: D+A */
};
/* 每圈步数(参考实测值)*/
#define STEPPER_STEPS_PER_REV 509U
/* 私有:将励磁状态写入 4 个引脚 */
static void stepper_write_phase(uint8_t seq)
{
HAL_GPIO_WritePin(STEPPER_PIN0_PORT, STEPPER_PIN0_PIN,
(seq & 0x01) ? HAL_GPIO_PIN_SET : HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN1_PORT, STEPPER_PIN1_PIN,
(seq & 0x02) ? HAL_GPIO_PIN_SET : HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN2_PORT, STEPPER_PIN2_PIN,
(seq & 0x04) ? HAL_GPIO_PIN_SET : HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN3_PORT, STEPPER_PIN3_PIN,
(seq & 0x08) ? HAL_GPIO_PIN_SET : HAL_GPIO_PIN_RESET);
}
/**
* @brief 初始化步进电机
* @note 引脚已在 CubeMX 中配置为输出,需确保初始状态为低电平
*/
void stepper_init(void)
{
stepper_stop();
}
/**
* @brief 驱动电机旋转指定步数
* @note steps 单位为"完整序列周期"(8 个相位/周期)
* @param steps: 正数=顺时针,负数=逆时针
* @param delay_ms: 步间延时(ms)
*/
void stepper_step(int32_t steps, uint16_t delay_ms)
{
int32_t direction = (steps >= 0) ? 1 : -1;
uint32_t cycles = (steps >= 0) ? (uint32_t)steps : (uint32_t)(-steps);
uint32_t i, j;
for (i = 0; i < cycles; i++)
{
for (j = 0; j < 8; j++)
{
uint8_t phase_idx;
if (direction > 0)
{
phase_idx = j; /* 正序 0,1,2,...,7 */
}
else
{
phase_idx = 7 - j; /* 逆序 7,6,5,...,0 */
}
stepper_write_phase(step_sequence[phase_idx]);
HAL_Delay(delay_ms);
}
}
}
/**
* @brief 旋转指定角度
* @param angle: 正数=顺时针,负数=逆时针
* @param delay_ms: 步间延时(ms)
*/
void stepper_rotate(int32_t angle, uint16_t delay_ms)
{
int32_t steps = (int32_t)(angle * (STEPPER_STEPS_PER_REV / 360.0f));
stepper_step(steps, delay_ms);
}
/**
* @brief 停止电机:所有线圈断电(释放扭矩)
*/
void stepper_stop(void)
{
HAL_GPIO_WritePin(STEPPER_PIN0_PORT, STEPPER_PIN0_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN1_PORT, STEPPER_PIN1_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN2_PORT, STEPPER_PIN2_PIN, HAL_GPIO_PIN_RESET);
HAL_GPIO_WritePin(STEPPER_PIN3_PORT, STEPPER_PIN3_PIN, HAL_GPIO_PIN_RESET);
}
/**
* @brief 测试:正转 90° → 停 → 反转 90° → 停,循环
* @note 用于验证电机接线和驱动是否正常
*/
void stepper_test(void)
{
/* 顺时针转 90° */
stepper_rotate(90, 2);
stepper_stop();
HAL_Delay(500);
/* 逆时针转 90° */
stepper_rotate(-90, 2);
stepper_stop();
HAL_Delay(500);
}保存代码。
打开项目的 CMakeLists.txt 文件,将新增的驱动文件 stepmotor.c 添加至编译路径;
# Stubs to be able to add compiler flag overrides, e.g. -Os
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add additional source files here
stepmotor.c
)保存文件。
测试
修改 main.c 文件,添加如下代码
int main(void)
{
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/* 初始化步进电机 */
stepper_init();
while (1)
{
/* 旋转测试 */
printf("Stepper test: CW 90deg -> CCW 90deg...\r\n");
stepper_test();
}
}
}保存代码,编译并上传固件。
复位开发板,运行程序;
步进电机正反交替旋转;


角度控制
进一步通过串口中断实现步进电机旋转角度和速度的精确控制。
流程图

代码
修改 main.c 代码如下
#include "main.h"
#include "stepmotor.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define RX_TIMEOUT 10000U
#define TX_TIMEOUT 1000U
#define MAX_BUFFER_SIZE 128U
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
volatile bool uart_rx_ready = false;
uint8_t uart_rx_buf[MAX_BUFFER_SIZE] = {0};
uint32_t uart_rx_len = 0;
uint8_t rx_temp = 0; /* 变量用于串口接收 */
int target_angle = 0;
uint32_t target_speed = 1; /* 步间延时(ms) */
/* Private functions prototype -----------------------------------------------*/
int __io_getchar(void) {
uint8_t ch = 0;
HAL_UART_Receive(mx_usart2_uart_gethandle(), (uint8_t *)&ch, 1, 10000);
return ch;
}
int __io_putchar(int ch) {
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)&ch, 1, 100);
return ch;
}
/**
* @brief UART 接收中断回调
*/
void HAL_UART_RxCpltCallback(hal_uart_handle_t *huart, uint32_t size_byte, hal_uart_rx_event_types_t rx_event)
{
uint8_t ch = rx_temp;
if (uart_rx_len < sizeof(uart_rx_buf) - 1)
{
uart_rx_buf[uart_rx_len++] = ch;
}
/* 收到换行符或大括号(JSON结束)标记接收完成 */
if (ch == '\n' || ch == '}')
{
uart_rx_buf[uart_rx_len] = '\0'; /* 添加字符串结束符 */
uart_rx_ready = true;
}
/* 继续接收下一个字节 */
HAL_UART_Receive_IT(mx_usart2_uart_gethandle(), &rx_temp, 1);
}
/**
* @brief 解析 JSON 命令并执行电机动作
*/
static void parse_json_cmd(int *angle, uint32_t *speed)
{
char *p = (char *)uart_rx_buf;
/* 解析两种格式:带空格和不带空格 */
if (sscanf(p, "{\"angle\":%d,\"speed\":%lu}", angle, speed) != 2)
{
sscanf(p, "{\"angle\": %d, \"speed\": %lu}", angle, speed);
}
}
/**
* @brief 主程序
*/
int main(void)
{
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
/* 初始化步进电机 */
stepper_init();
printf("\r\n=== Stepper Motor JSON Command Test ===\r\n");
printf("Usage: {\"angle\": 5, \"speed\": 1.2}\r\n");
printf("angle>0 CW, angle<0 CCW, speed=ms/step\r\n\r\n");
/* 启动 UART 中断接收 */
HAL_UART_Receive_IT(mx_usart2_uart_gethandle(), &rx_temp, 1);
while (1) {
if (uart_rx_ready)
{
/* 解析指令 */
parse_json_cmd(&target_angle, &target_speed);
printf("OK: angle=%d, speed=%lu\r\n", target_angle, target_speed);
/* 电机转动:正角度=顺时针,负角度=逆时针 */
stepper_rotate(target_angle, (uint16_t)target_speed);
stepper_stop();
/* 清空接收缓冲区 */
memset(uart_rx_buf, 0, sizeof(uart_rx_buf));
uart_rx_len = 0;
uart_rx_ready = false;
}
HAL_Delay(10);
}
}
} /* end main */保存代码。
效果
重新编译运行程序;
打开串口调试助手,根据提示输入 JSON 格式指令,如 {"angle": -90, "speed": 1} ,点击发送;


步进电机根据指令,以 1ms/step 的速度逆时针旋转 90 度;


总结
本文介绍了 Let's Do|2026年第3期 活动,通过主控 NUCLEO-C562RE 开发板实现 28BYJ48 步进电机驱动和串口控制的项目设计,包括硬件连接、环境搭建、工程测试等流程,为后续项目的研究探索提供了参考。
我要赚赏金
