2025 Make Blocks阶段2 - FOC步进电机驱动器
这是一款安装在42步进电机背面的FOC驱动器,与电机相同尺寸,支持电流环、速度环、位置环三环FOC控制,支持多种通讯接口,包括CAN,MODBUS,RS485,UART,I2C以及常见的EN、DIR、STP控制,以及基于WIF的MQTT。
标签
嵌入式系统
MQTT
CAN
FOC
RS485
StreakingJerry
更新2025-10-17
6
KiCad文件
全屏

硬件和功能介绍

这是一款安装在42步进电机背面的FOC驱动器,与电机相同尺寸,支持电流环、速度环、位置环三环FOC控制,支持多种通讯接口,包括CAN,MODBUS,RS485,UART,I2C以及常见的EN、DIR、STP控制,以及基于WIF的MQTT。


我曾在一年前制作过一个FOC步进电机驱动,当时使用的驱动芯片是TI的DRV8962。后续使用中发现一系列问题。主要的几个问题如下:

1,DRV8962使用并不广泛,器件昂贵且难以购买。

2,DRV8962内置电流镜检测的是高端电流,但高端电流检测目前并没有受到大多数开源电机控制库的支持。

3,板载UART-USB接口虽然在调试阶段增加便利性,但是却让日后添加UART外设传感器变得非常不便。

4,5V稳压二极管虽然能对接口带来保护,但在RS485, CAN等差模信号遇到较高的共模干扰时,稳压二极管反而会造成通信异常。

因此在本次活动中我更换方案,重新设计了这款驱动器。

模块设计思路、选型及框图介绍

本项目使用的核心模块有两个,其中主控使用ESP32-S3,电机驱动使用的是两片DRV8874 H桥驱动器,支持6A电流驱动。

ESP32-S3-WROOM-1-N16R8: https://www.digikey.hk/zh/products/detail/espressif-systems/ESP32-S3-WROOM-1-N16R8/16162642

DRV8874: https://www.digikey.hk/zh/products/detail/texas-instruments/drv8874pwpr/11502339

DRV8874的集成度非常高,上面已经集成了低边电流测量功能,板载再加一个编码器识别电机角度即可。我选择的是AS5047P非接触式磁编码器。


其他的模块是为了一些通信和调试功能服务。

image.png


模块原理图、PCB简单介绍

先看一下原理图全貌,每个功能区域都单独用框框了起来。MCU引脚分配也都使用标签桥接单独列出,方便查询及修改。

image.png

下面每一个部分讲一下设计需要特别注意的地方:

image.png

ESP32-S3-WROOM模块要特别注意strapping引脚的电平配置。虽然默认内置了弱上/下拉,但我实际使用中发现内置弱上/下拉力度太弱,非常容易被干扰导致启动或者刷血异常,因此还是最好通过外置电阻进行配置。同时,在使用这些strapping管脚时要特别注意,最好不要用做输入功能,以免被其他设备上电默认输出电平影响启动,另外在作为输出引脚使用时也要注意相关设备引脚的默认上/下拉配置。

image.png

电机驱动模式尽可能不要用电阻配置死,而是直接连接单片机,靠单片机来进行配置。这里两个PMODE引脚使用了ESP32-S3上的strapping管脚,通过查询规格书可以看到这个管脚没有上下拉配置,可以放心使用。

image.png

image.png

电源设计注意输出电容要选一个大容值的钽电容或者固态电容。不能直接用陶瓷电容,否则ESR不满足要求,需要修改反馈回路。其他原理图上没什么讲究,更多的讲就是在PCB Layout上。

image.png

AS5047P使用SPI进行通信,效率比较高。连接ESP32-S3时尽量使用硬件VSPI,以获得最好的效果。

image.png

image.png

RS485和CAN通信部分添加了可选的终端电阻接入跳线帽,方便使用。


image.png

image.png

板子接口设计的比较多样化,调试和烧录固件可以试用USB或者UART,板载了RST和BOOT按钮可以方便快速进入烧写模式。另外使用2.54双排针引出GPIO,可以方便使用一些ADC, SPI, I2C, UART等传感器或者额外的执行部件例如继电器,LED等。主要的通信端口,电源端口和电机端口使用的KF128螺栓端子,工业领域较为常见且通用度高,非常灵活。


最后再看一下PCB,设计的比较小巧,又要满足部分线路的大电流和抗干扰,layout上讲究还是比较多的,大家直接看图,我就不过多赘述了。

image.png

image.png

调试软件介绍、关键代码片段及说明

由于485, CAN, MQTT等我在之前的文章中已经做过演示,这里就不再追溯了,感兴趣的可以看看我之前的项目:

https://www.eetree.cn/project/3519


这里主要是增加了电流环的配置,以及重写了硬件配置部分,让代码可读性更高。

#include <SimpleFOC.h>
#include <CanSerial.h>
#include <MQTTSerial.h>


// Driver
#define MODE_A_PIN 45
#define NSLEEP_A_PIN 21
#define NFAULT_A_PIN 14
#define ISENSE_A_PIN 9
#define IN1_A_PIN 47
#define IN2_A_PIN 48


#define MODE_B_PIN 46
#define NSLEEP_B_PIN 7
#define NFAULT_B_PIN 6
#define ISENSE_B_PIN 3
#define IN1_B_PIN 16
#define IN2_B_PIN 15


StepperMotor motor = StepperMotor(50);
StepperDriver4PWM driver = StepperDriver4PWM(IN1_A_PIN, IN2_A_PIN, IN1_B_PIN, IN2_B_PIN);
LowsideCurrentSense current_sense = LowsideCurrentSense(540, ISENSE_A_PIN, ISENSE_B_PIN);


// instantiate the sensor
#define SENSOR_CS_PIN 10
MagneticSensorSPI sensor = MagneticSensorSPI(AS5047_SPI, SENSOR_CS_PIN);


// instantiate the commander
Commander commander = Commander(Serial);
void onMotor(char *cmd) {
  commander.motor(&motor, cmd);
}


// instantiate the step dir interface
#define STEP_PIN 39
#define DIR_PIN 40
StepDirListener step_dir = StepDirListener(STEP_PIN, DIR_PIN, _2PI / 200.0);
void onStep() {
  step_dir.handle();
}


// instantiate the RS485 interface
#define RS485_RX_PIN 18
#define RS485_TX_PIN 17
#define RS485_RTS_PIN 8
#define RS485_BAUD 9600
#define RS485 Serial1
Commander commander_rs485 = Commander(RS485);
void onMotor_rs485(char *cmd) {
  commander_rs485.motor(&motor, cmd);
}


// instantiate the CAN interface
#define CAN_RX_PIN 4
#define CAN_TX_PIN 5
#define CAN_SPEED 500
#define CAN_ID 'A'
CanSerial CAN;
Commander commander_can = Commander(CAN);
void onMotor_can(char *cmd) {
  commander_can.motor(&motor, cmd);
}


// instantiate the MQTT interface
#define SSID ""
#define PASSWORD ""
#define MQTT_SERVER "192.168.x.x"
#define PORT 1883
#define MQTT_USR ""
#define MQTT_PWD ""
#define CLIENT_ID "stepper"
#define TOPIC_RX "stepper/rx"
#define TOPIC_TX "stepper/tx"
MQTTSerial MQTT;
Commander commander_mqtt = Commander(MQTT);
void onMotor_mqtt(char *cmd) {
  commander_mqtt.motor(&motor, cmd);
}


void setup() {
  // use monitoring with serial
  Serial.begin(115200);
  // while (!Serial)
  // {
  //     delay(10);
  // }
  SimpleFOCDebug::enable(&Serial);
  // motor.useMonitoring(Serial);


  // driver config
  pinMode(MODE_A_PIN, OUTPUT);
  pinMode(NSLEEP_A_PIN, OUTPUT);
  pinMode(NFAULT_A_PIN, INPUT_PULLUP);
  pinMode(MODE_B_PIN, OUTPUT);
  pinMode(NSLEEP_B_PIN, OUTPUT);
  pinMode(NFAULT_B_PIN, INPUT_PULLUP);


  bool mode = HIGH; //Low = PH/EN; High = PWM; Hi-Z = Independent Half-Bridge
  digitalWrite(MODE_A_PIN, mode);
  digitalWrite(MODE_B_PIN, mode);


  bool nsleep = HIGH; //Low = sleep; High = enable device
  digitalWrite(NSLEEP_A_PIN, nsleep);
  digitalWrite(NSLEEP_B_PIN, nsleep);


  driver.voltage_power_supply = 12;
  if (!driver.init()) {
    Serial.println("Driver init failed!");
  }
  motor.linkDriver(&driver);


  // current sensor config
  current_sense.linkDriver(&driver);
  if (!current_sense.init()) {
    Serial.println("Current sense init failed!");
  }
  motor.linkCurrentSense(&current_sense);


  // position sensor config
  sensor.init();
  motor.linkSensor(&sensor);


  // motor config
  motor.voltage_sensor_align = driver.voltage_power_supply * 0.8;
  motor.torque_controller = TorqueControlType::voltage;
  motor.controller = MotionControlType::velocity;


  // motor.PID_velocity.P = 0.2;
  // motor.PID_velocity.I = 20;
  // motor.PID_velocity.D = 0.001;
  // motor.PID_velocity.output_ramp = 1000;
  // motor.LPF_velocity.Tf = 0.01;


  // motor.P_angle.P = 20;
  // motor.P_angle.I = 0;
  // motor.P_angle.D = 0;
  // motor.P_angle.output_ramp = 10000;
  // motor.LPF_angle.Tf = 0;


  // motor.motion_downsample = 100;


  if (!motor.init()) {
    Serial.println("Motor init failed!");
  }
  if (!motor.initFOC()) {
    Serial.println("FOC init failed!");
  }


  // init serial interface
  commander.add('M', onMotor, "full motor config");


  // // init step and dir pins
  // step_dir.init();
  // step_dir.enableInterrupt(onStep);
  // step_dir.attach(&motor.target);


  // init RS485
  RS485.begin(RS485_BAUD, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
  while (!RS485) {
    delay(10);
  }
  if (!RS485.setPins(-1, -1, -1, RS485_RTS_PIN)) {
    Serial.print("Failed to set RS485 pins");
  }
  if (!RS485.setMode(UART_MODE_RS485_HALF_DUPLEX)) {
    Serial.print("Failed to set RS485 mode");
  }
  commander_rs485.add('M', onMotor_rs485, "full motor config");


  // init CAN
  if (!CAN.begin(CAN_RX_PIN, CAN_TX_PIN, CAN_SPEED, CAN_ID)) {
    Serial.println("CAN bus failed!");
  }
  commander_can.add('M', onMotor_can, "full motor config");


  // // init MQTT
  // MQTT.begin(SSID, PASSWORD, MQTT_SERVER, PORT, MQTT_USR, MQTT_PWD, CLIENT_ID, TOPIC_RX, TOPIC_TX);
  // commander_mqtt.add('M', onMotor_mqtt, "full motor config");


  Serial.println("Setup Done!");
}
 
void loop() {
  motor.loopFOC();
  motor.move();
  commander.run();
  commander_rs485.run();
  commander_can.run();
  // MQTT.loop();
  // commander_mqtt.run();


  // motor.monitor();
}

模块调通的功能展示图

微信图片_20251005012025_2_14.jpg

具体的功能演示,由于需要观察电机的动态情况,图片难以展示,大家可以直接参考视频中的内容。

心得体会

时隔一年再次参加电子森林的活动,活动依旧创意十足,诚意满满!

附件下载
ProDoc_步进V2_2025-08-10.kicad_sch
ProDoc_步进V2_2025-08-10.kicad_pcb
FOC_Stepper_v2.rar
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号