这篇文章解决一个问题:网上步进电机的教程一大堆,但没几个把“电机转起来”和“平台精确到位”说清楚。用42步进电机做丝杆滑台,你会立刻撞上开环控制的三个坑:丢步、回程间隙、联轴器打滑。本文从开环一路做到闭环,提升重复定位精度,再接入大夏龙雀 DX-NR11 2.4G 无线串口模组,让平台支持远程遥控——一套“精确定位 + 无线控制”的完整闭环系统。

适用对象:会用 STM32CubeMX / HAL 库、想玩机电一体化的学生和创客。

你会得到:完整接线、脉冲当量计算、开环 / 闭环 C 代码、实测数据表、踩坑清单。

前置条件:STM32F405RGT6 最小系统板 + HAL 库基础;机械部分可直接用成品丝杆滑台。

项目背景:为什么开环的丝杆滑台“不听话”

很多人以为“发了 N 个脉冲,滑台就该走 N 步”。实际工程里根本不是这样,主要误差源有三个:

  • 失步(丢步):电机启动加速度太大、负载过重或速度超过矩频特性,转子跟不上脉冲直接丢步——脉冲发出去了,轴没转。

  • 回程间隙:丝杆和螺母之间有间隙,换向时先空转一段才带动工作台,造成“正着走准、反着走偏”。

  • 传动打滑:刚性不够时电机轴转了,丝杆端没跟上。

所以开环只能“大概准”。要真正精确,就得把实际位置量出来,反馈给单片机做闭环——这就是本项目的核心。

硬件清单与接线

电子硬件清单

部件

型号 / 规格

作用

主控

STM32F405RGT6 最小系统板

控制核心

步进电机

42BYGH,1.8°,两相四线

驱动丝杆

驱动器

TB6600

脉冲 → 驱动电流

丝杆滑台

T8 丝杆(导程 8mm)+ 光轴×2 + 滑块

直线进给机构

限位开关

轻触开关 ×2

回零

磁编码器

AS5600 模块

丝杆转角反馈

磁铁

径向充磁圆磁铁 D6

配合 AS5600

通信模块

DX-NR11-24T12S(2.4G 无线串口)

无线遥控

电源

12V 3A

驱动供电

线材

杜邦线、接线端子

连接

引脚定义

STM32 引脚

连接对象

说明

PA0

TIM2_CH1 → TB6600 PUL

步进脉冲(硬件 PWM)

PB0

TB6600 DIR

运动方向

PB1

TB6600 ENA

使能(低有效)

PB12

左限位开关

回零原点

PB13

右限位开关

行程保护

PB6

I2C1 SCL(AS5600 + OLED)

I2C 时钟,总线共用

PB7

I2C1 SDA(AS5600 + OLED)

I2C 数据,总线共用

PA2

USART2 TX → NR11 RXD

串口发送(3.3V 直连)

PA3

USART2 RX ← NR11 TXD

串口接收无线命令

系统架构

执行链是“STM32发脉冲 → TB6600 驱动 → 步进电机 → 丝杆 → 工作台”;反馈链是“丝杆转角 → AS5600 磁编码器 → 主控”,构成位置闭环。限位开关负责回零和防撞。此外,STM32 的串口接一块 DX-NR11 2.4G 无线串口模组:遥控端的 NR11(电脑 USB-TTL)通过 2.4G 空中链路与滑台端透传命令帧,实现远程定位、回零与坐标回读——本地闭环 + 无线遥控,就是这套平台的核心功能。

关键参数:脉冲当量怎么算

先做开环:让滑台按脉冲走

开环的核心是“用定时器、硬件 PWM 按固定频率输出 PUL 脉冲”。代码如下:

// CubeMX 配置:TIM2 通道1 PWM 输出到 PA0,预分频 0
// TIM2 时钟 = APB1×2 = 84MHz,PWM 频率 = 84MHz / (ARR+1)
volatile uint32_t pulse_remain;   // 剩余脉冲数
volatile uint8_t  is_moving;      // 运动标志

// TIM2 的 CC 中断:每个 PWM 周期 = 1 个脉冲
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        if (pulse_remain > 0)
        {
            pulse_remain--;
            if (pulse_remain == 0)
            {
                is_moving = 0;
                HAL_TIM_PWM_Stop_IT(&htim2, TIM_CHANNEL_1);  // 停发脉冲
            }
        }
    }
}

// 设置脉冲频率(Hz)
void step_set_freq(uint32_t freq_hz)
{
    uint32_t arr = 84000000UL / freq_hz - 1;
    __HAL_TIM_SET_AUTORELOAD(&htim2, arr);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, arr / 2);  // 50% 占空比
}

// 按步数运动
void move_steps(int32_t steps, uint32_t freq_hz)
{
    if (steps == 0) return;
    HAL_GPIO_WritePin(DIR_GPIO_Port, DIR_Pin,
                      (steps > 0) ? GPIO_PIN_SET : GPIO_PIN_RESET);
    pulse_remain = (steps > 0) ? steps : -steps;
    step_set_freq(freq_hz);
    is_moving = 1;
    HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_1);  // 开 PWM 并开中断
    while (is_moving);             // 阻塞等待
}

回零逻辑:启动时向负方向低速走,直到触发左限位(PB12),把当前位置记为原点,同时清零 AS5600 累计值。

闭环改造:AS5600 磁编码器反馈

为什么反馈装在丝杆上而不是电机上

装在电机轴上只能证明“电机转了”,证明不了“丝杆转了”。联轴器打滑、丝杆间隙都不会在电机端体现,所以必须测最终输出——丝杆转角。

AS5600 安装

3D 打印一个小支架,把径向充磁的圆磁铁粘在丝杆末端,AS5600 模块正对磁铁,固定在支架上。上电后用串口打印原始角度,转动丝杆确认读数连续变化、无跳变。

读取与多圈累计

// CubeMX 配置:I2C1(PB6=SCL / PB7=SDA),标准模式 100kHz
// AS5600 地址 0x36,OLED 地址 0x3C,共用同一总线
#define AS5600_ADDR  0x36
#define REG_ANGLE    0x0C   // RAW ANGLE (12bit)

uint16_t as5600_read(void)
{
    uint8_t reg = REG_ANGLE;
    uint8_t buf[2];
    HAL_I2C_Master_Transmit(&hi2c1, (AS5600_ADDR << 1), &reg, 1, 10);
    HAL_I2C_Master_Receive (&hi2c1, (AS5600_ADDR << 1) | 1, buf, 2, 10);
    return ((uint16_t)buf[0] << 8 | buf[1]) & 0x0FFF;
}

// 多圈累计
static uint16_t last_angle;
volatile int32_t total_cnt;

void sample_position(void)
{
    uint16_t a = as5600_read();
    int16_t diff = (int16_t)(a - last_angle);
    if (diff >  2048) diff -= 4096;  // 跨零回绕
    if (diff < -2048) diff += 4096;
    total_cnt += diff;
    last_angle = a;
}

位置环:误差补脉冲 + 回程间隙补偿

#define PULSE_PER_CNT  (3200.0f / 4096.0f)  // 1 个编码器计数 ≈ 0.78 个脉冲
volatile int32_t target_cnt;   // 目标位置
volatile int32_t act_cnt;      // 实际位置
#define DEAD_ZONE  3         


void position_loop(void)
{
    sample_position();
    act_cnt = total_cnt;
    int32_t err = target_cnt - act_cnt;
    if (err > DEAD_ZONE || err < -DEAD_ZONE)
    {
        int32_t need = (int32_t)(err * PULSE_PER_CNT);
        move_steps(need, 300);   // 低速补齐
    }
}


#define BACKLASH_CNT 52   // 标定值
static uint8_t last_dir = 0;
void backlash_comp(uint8_t dir)
{
    if (dir != last_dir)
    {
        int32_t gap = dir ? BACKLASH_CNT : -BACKLASH_CNT;
        move_steps((int32_t)(gap * PULSE_PER_CNT), 200);
    }
    last_dir = dir;
}

主循环里周期性调用 position_loop(),把目标位置与实测位置之差换算成脉冲补上去,直到误差进入死区。间隙补偿在每次换向前先“预走”一个实测间隙量。

闭环实测

测试项

命令位移 (mm)

实测位移 (mm)

正向 10

10.000

10.002

正向 20

20.000

19.998

正向 30

30.000

30.004

高速 (2000pps) 20

20.000

20.011

往返重复定位 ×10

回到 0

≤0.018

结论:闭环后高速丢步被拉回(20.011mm),重复定位从 ±0.1mm 级别收敛到 ≤0.02mm。对 2.5μm 理论分辨率来说,剩余误差主要来自丝杆本身的间隙与 AS5600 的标定误差。

自制STM32F405RGT6核心板设计

最小系统板原理图

实拍图

无线遥控:NR11 2.4G 远程控制

定位闭环只解决了“本地精确”,要让这台平台真正“有用”,还得能远程控制。这里引入大夏龙雀 DX-NR11-24T12S:一块 2.4G 无线串口模组,板载 PCB 天线、半双工、默认 9600 波特率透明传输,空旷距离可达 200m。滑台端一块 NR11 接 STM32 的串口,遥控端再放一块 NR11(电脑 USB-TTL,或与 BT121 桥接连手机),两端同信道同地址即可透传——STM32 端解析命令帧执行定位,再把坐标回传。

接线与电平匹配

NR11 是 3.3V 逻辑器件(供电 2.5~3.6V,典型 3.3V),STM32F405 同为 3.3V,电平天然匹配,TXD、RXD 直连即可:

连接

说明

STM32 PA2(USART2_TX)→ NR11 RXD

同为 3.3V 电平,直连

STM32 PA3(USART2_RX)← NR11 TXD

3.3V 电平,直连

NR11 VCC

接 STM32 板 3.3V

NR11 GND

与 STM32 共地

NR11 直接取 STM32 板的 3.3V 电源即可;注意 NR11 模块供电超过 3.6V 有烧毁风险。

模块配置(AT 指令)

两端模块出厂默认射频参数一致(空中速率 2Mbps、信道 000、地址 10,11),不改也能直接透传;要换信道、地址隔离干扰,进入 AT 模式配置:


通信协议(自定义帧)

透明传输只负责“传字节”,协议要自己定。这里用 6 字节定长帧,末尾带校验防误触发:

字段

长度

说明

0xAA 0x55

2B

帧头

CMD

1B

命令码

D1 D2

2B

参数:目标位置,单位 0.01mm

SUM

1B

校验:AA^55^CMD^D1^D2

CMD

功能

参数

0x01

回零

0x02

定位

D1D2 = 目标位置 ×100(0.01mm)

0x03

查询位置

无,回传坐标帧

0x04

急停

例:定位到 20.00mm,目标值 2000 = 0x07D0,命令帧为 AA 55 02 07 D0 2A(SUM = AA^55^02^07^D0 = 0x2A)。

STM32 端程序

STM32 用 USART2 接收,接收用状态机逐字节解析帧头、命令和校验:

// CubeMX 配置:USART2(PA2=TX / PA3=RX),9600, 8, N, 1
volatile uint8_t rx_byte;
uint8_t rx_state = 0;   // 接收状态机
uint8_t rx_cmd, rx_d1, rx_d2;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART2)
    {
        uint8_t c = rx_byte;
        switch (rx_state)
        {
            case 0: rx_state = (c == 0xAA) ? 1 : 0; break;
            case 1: rx_state = (c == 0x55) ? 2 : 0; break;
            case 2: rx_cmd = c; rx_state = 3; break;
            case 3: rx_d1  = c; rx_state = 4; break;
            case 4: rx_d2  = c; rx_state = 5; break;
            case 5:
                if ((0xAA ^ 0x55 ^ rx_cmd ^ rx_d1 ^ rx_d2) == c)
                    handle_cmd(rx_cmd, (uint16_t)(rx_d1 << 8 | rx_d2));
                rx_state = 0;
                break;
        }
        HAL_UART_Receive_IT(&huart2, &rx_byte, 1);   // 继续接收
    }
}

void uart_send(uint8_t c)
{
    HAL_UART_Transmit(&huart2, &c, 1, 10);
}

效果验证

电脑端串口助手(sscom)接 USB-TTL + NR11,发一帧 AA 55 02 07 D0 2A,滑台走到 20.00mm 并回传坐标。

开发记录

  • AS5600 读数跳变:磁铁偏心或用了轴向充磁,换成径向充磁磁铁并确保对中。

  • TB6600 大电流时主控复位:主控与驱动分电源供电、共地。

  • 限位开关必须接:第一次调试把最大速度调低,限位触发立即停,防止撞机顶坏丝杆。

  • NR11 是 3.3V 器件:不能 5V 直供;与 STM32 同为 3.3V 电平,TXD / RXD 直连即可。

  • NR11同信道同地址才能通信:两端射频参数不一致就“静默”,先发 AT 确认模块在线。

  • 天线避开金属:NR11 用板载天线,别贴金属外壳或大面积铺铜,天线方向留至少 5mm 净空。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐