- 嵌入式
- 硬件开发
- 物联网
- 工业制造
【免费下载链接】Grbl_Esp32
A port of Grbl CNC Firmware for ESP32
在 Grbl_Esp32 中通过 RS485 总线控制 H2A/H2B/H2C(手册中又称 P2A/P2B/P2C)系列变频器(VFD)主轴时,厂商手册对协议细节的描述常常含糊不清,容易出现字节序、奇偶校验、命令地址等陷阱。本文以仓库中的协议文档 H2ASpindle.md 为主体,完整讲解 MAX485 接线、变频器 F 参数配置、Modbus 帧结构与 CRC 字节序、常用命令序列,并结合 H2ASpindle.cpp 与 VFDSpindle.cpp 的源码,说明这些协议规则在 Grbl_Esp32 驱动中是如何落地的,帮助读者既会配变频器,也看得懂固件的通信逻辑。
为什么需要这份协议文档
H2ASpindle.md 是 H2A 驱动自带的协议补全文档。驱动源码文件 H2ASpindle.cpp 的头部注释也明确指出:官方文档"一旦看懂了其实还行,但不幸的是并不完整",协议文档中缺失的部分正是作者拼凑整理出来的这份 md 文件。换句话说,它是 H2A/H2B/H2C 变频器 Modbus 通信的"实战勘误表",覆盖了三个关键主题:
- RS485 硬件接线方式(以及 ESP32 电平兼容问题);
- 变频器必须预先写入的 F 参数(通信地址、波特率、奇偶校验、Modbus 模式);
- 命令帧格式、CRC 字节顺序,以及最常用的启停、调速、查询命令序列。
文档末尾还给出了一条重要的实现原则:发送/接收冲突会破坏校验和,且变频器收到命令后执行需要时间,因此任何命令下发后都应轮询实际值确认生效——这条原则在 Grbl_Esp32 的 VFD 基类中得到了完整实现,后文会详细对照。
RS485 硬件接线
Grbl_Esp32 是 ESP32 平台,而 MAX485 收发器通常工作在 5V。按照文档给出的接线表,使用一块 MAX485 模块时各引脚应这样连接:
| MAX485 引脚 | 连接目标 | 说明 |
|---|---|---|
| GND | 主控板 GND | 共地 |
| A | RS+485 | 差分信号正 |
| B | RS-485 | 差分信号负 |
| VCC | 主控板 5V | 5V 供电 |
| DI | TX | 任意 D 引脚,3.3V 或 5V 信号均可 |
| DE | 与 RE 短接后接某一引脚 | 发送方向控制,任意 D 引脚,3.3V 或 5V 信号 |
| RE | (与 DE 短接) | 接收使能 |
| RO | RX | 任意 D 引脚,3.3V 或 5V 信号 |
文档特别警告:ESP32 无法承受 5V 电平。DI/RO 等数据引脚虽然标注"3.3V or 5V",但 RO 输出到 ESP32 的 RX 时必须加电平转换,或者干脆选用 3.3V 版本的 RS485 模块,否则有损坏芯片的风险。
在 Grbl_Esp32 的固件侧,这三根信号线(TX、RX、RTS/方向控制)通过机器定义文件中的宏指定,例如 4axis_external_driver.h 中的定义:
#define VFD_RS485_TXD_PIN GPIO_NUM_17 #define VFD_RS485_RXD_PIN GPIO_NUM_4 #define VFD_RS485_RTS_PIN GPIO_NUM_16如果宏未定义,VFDSpindle.cpp 的get_pins_and_settings()会逐一打印Undefined VFD_RS485_TXD_PIN等提示并拒绝初始化。另外从源码结构看,VFD 固定使用 UART2 端口(VFD_RS485_UART_PORT = 2,见 VFDSpindle.cpp),且init()会调用setHalfDuplex()让 ESP32 的 UART 驱动自动管理收发方向,因此 DE/RE 短接的那根控制线就是 RTS 引脚。
变频器 F 参数配置
文档反复强调:务必先读变频器手册,转速上限、运行模式等都必须正确;同时要让 RS485 通信工作,必须先写入以下 F 参数(这是整个协议能跑通的前提):
| F 参数 | 值 | 含义 |
|---|---|---|
| F0.02 | 7 | 启用 RS485 |
| F0.04 | 2 | 启用 RS485 |
| F0.09 | 4 | 启用 RS485 |
| F9.00 | 4 | 波特率 19200 |
| F9.01 | 0 | 8,N,1 无校验(原文面向 Arduino SoftwareSerial 场景);使用硬件串口时"任选合适的即可" |
| F9.02 | 1 | 从机地址 |
| F9.05 | 0 | 非标准 Modbus(1 = 标准 Modbus,2 = ASCII) |
| F9.07 | 0 | 写操作需要回包确认 |
关于波特率,文档的观点是:19200-8N1 对 VFD 完全够用,更高波特率只会带来更多错误,但对某些上位机框架(如 Marlin)来说高波特率能保持通信"心情舒畅",这是个折中取舍。
关于奇偶校验,文档给出了一条实用结论:如果用 SoftwareSerial 则只能用 8N1;如果走硬件串口,应该设置 8E1,因为这才是 VFD 的默认值。这一点在 Grbl_Esp32 中得到了印证——H2A类的构造函数把串口参数固定为 19200 波特率 + Even(偶校验):
// H2ASpindle.cpp H2A::H2A() : VFD() { _baudrate = 19200; _parity = Uart::Parity::Even; }见 H2ASpindle.cpp。也就是说,在 Grbl_Esp32 上配置 H2A 变频器时,应遵循文档把 F9.00 设为 4(19200),并把 F9.01 配成与 8E1 对应的选项,与固件硬编码的 Even 校验保持一致。
帧结构与 CRC 字节序
H2A 协议基于 Modbus RTU。写一帧命令的字节排布,文档用一个"反向旋转运行"的例子说明得最清楚:
01: 设备地址,通常为 1 06: 功能码: 03 = 读, 06 = 写, 07 = 命令 20: 0x2000(设置命令)的第 1 字节 00: 0x2000 的第 2 字节 00: 0x0002(反向运行)的第 1 字节 02: 0x0002 的第 2 字节 03: crc_value & 0xFF —— CRC 低字节在前 CB: (crc_value >> 8) & 0xFF —— CRC 高字节在后作者把这包简记为01.06.2000.0002。文档特别提醒,厂商资料经常把 Big Endian 与 Little Endian 搞混,上述"CRC 低字节在前"的顺序才是正确的。
Grbl_Esp32 的 VFD 基类实现了标准 Modbus RTU CRC16(种子 0xFFFF、多项式 0xA001),见 VFDSpindle.cpp 的ModRTU_CRC(),并且在组帧时把 CRC 按小端写入消息尾部:
// VFDSpindle.cpp, vfd_cmd_task 内 auto crc16 = ModRTU_CRC(next_cmd.msg, next_cmd.tx_length); next_cmd.tx_length += 2; next_cmd.rx_length += 2; next_cmd.msg[next_cmd.tx_length - 1] = (crc16 & 0xFF00) >> 8; // 高字节 next_cmd.msg[next_cmd.tx_length - 2] = (crc16 & 0xFF); // 低字节见 VFDSpindle.cpp。接收侧同样会重算 CRC 并连同"长度、地址首字节"一起做三重校验(见 VFDSpindle.cpp),与文档中"字节序别搞错,否则校验和必坏"的告诫完全对应。
常用命令序列
文档指出:大多数命令的格式是"设备地址(ID) + 命令 + 想要的返回值个数",例如读 4 个值:
Send: 01 03 3000 0004 // 命令 0x3000, 请求 4 个返回值 Recv: 01 03 0008 0002 0000 0000 0000 D285 #bytes #1 #2 #3 #4 CRC查询运行状态的命令是 0x3000,状态本身只有 1 个字节,所以参数可以直接写0001。文档同时提醒:写非易失存储、改速度等场景存在例外,不能机械套用这个"数值个数"规则。
初始化与状态查询
获取当前状态:01 03 3000 0001,响应包含 1 个状态值,取值枚举如下:
03= 空闲(idle)01= 正向运行(forward running)02= 反向运行(reverse running)
文档示例:
Send: 01 03 30 00 00 01 8B 0A Recv: 01 03 00 02 00 02 65 CB获取最大转速(B0.05):01 03 B005 0002,示例响应:
Send: 01 03 B0 05 00 02 F2 CA Recv: 01 03 00 04 5D C0 03 F6 D0 21 -- -- = 24000即5D C0两个字节按大端拼出 24000 RPM。
获取当前转速(d0.12):01 03 700C 0002。注意当前转速与百分比并不是一一对应的。例如在 24000 RPM 的主轴上设置 10% 最大转速:
Send: 01 03 700C 0002 1EC8 Recv: 01 03 0004 095D 0000 D149 ---- 2397 RPM (~ 10%)运行控制与调速
| 操作 | 命令帧 |
|---|---|
| 正转 Forward run | 01 06 2000 0001 |
| 反转 Backward run | 01 06 2000 0002 |
| 停止 Stop | 01 06 2000 0006 |
| 设置速度 | 01 06 1000 xxxx |
速度命令xxxx的单位是最大速度的 1/100,即 10000 表示 100%(27 10两个字节),00000 表示最小速度。由于 F9.07 = 0 要求写操作必须回包,01 06写命令会原样回显整帧:
Send: 01 06 20 00 00 01 43 CA Recv: 01 06 20 00 00 01 43 CAGrbl_Esp32 中这些命令的源码映射
H2A 驱动类通过重写基类的虚函数把上述命令序列封装成固件可调用的接口,定义见 H2ASpindle.h。基类 VFDSpindle.h 提供了统一的ModbusCommand结构(16 字节消息缓冲 + 收发长度 + 关键标志)以及可重写的"响应解析器"回调:
struct ModbusCommand { bool critical; uint8_t tx_length; uint8_t rx_length; uint8_t msg[VFD_RS485_MAX_MSG_SIZE]; };对照文档,各命令在 H2ASpindle.cpp 中的实现一一对应:
方向命令(direction_command,L37-L47):01 06 2000 xx,其中 Ccw(反转)→0x02,Cw(正转)→0x01,其余(停机)→0x06,与文档的 Forward/Backward/Stop 三条序列完全一致:
data.msg[1] = 0x06; // WRITE data.msg[2] = 0x20; // Command ID 0x2000 data.msg[3] = 0x00; data.msg[4] = 0x00; data.msg[5] = (mode == SpindleState::Ccw) ? 0x02 : (mode == SpindleState::Cw ? 0x01 : 0x06);速度命令(set_speed_command,L49-L74):文档说速度是"最大值的 1/100",代码据此做了换算——把目标 RPM 按比例折算到[0, 10000]区间,其中 10000 代表 100%:
// Speed is in [0..10'000] where 10'000 = 100%. uint16_t speed = (uint32_t(rpm) * 10000L) / uint32_t(max_rpm); if (speed > 10000) speed = 10000; data.msg[1] = 0x06; // WRITE data.msg[2] = 0x10; // Command ID 0x1000 data.msg[4] = uint8_t(speed >> 8); data.msg[5] = uint8_t(speed & 0xFF);这里也体现了文档"当前 RPM 与百分比不是 1:1 关系"的说法:固件必须先知道_max_rpm,才能把绝对转速换算成变频器要的百分比值。
初始化序列(initialization_sequence,L76-L102):上电后第一条命令就是文档中的"获取最大 RPM"01 03 B005 0002,解析器从响应第 4、5 字节大端合成转速并写入_max_rpm,随后向串口打印H2A spindle is initialized at %d RPM。也就是说文档里"Recv: ... 5D C0 = 24000"的示例,正是这段代码期望收到的真实帧。
转速同步轮询(get_current_rpm,L104-L125):发送文档中的01 03 700C 0002,把响应中的当前转速写入_sync_rpm,供主线程判断主轴是否已到达目标转速。
方向查询(get_current_direction,L127-L144):发送01 03 3000 0001,即文档"Initialization & status"一节的状态查询命令。
另外两个能力开关也值得关注:supports_actual_rpm()返回true(H2A 支持读取真实转速),safety_polling()返回false——这意味着 H2A 不进入"RPM → 方向 → 状态"的周期性安全检查循环,空闲时通信任务只做轻量等待,只在转速同步阶段按需轮询get_current_rpm。
通信任务:重试、轮询与"等到实际值匹配"
文档"Implementation details"一节的核心忠告是:发送和接收一旦冲突,校验和就会被破坏;而且即使 VFD 收到了命令(比如从 FWD 切到 REV),执行也需要时间。所以永远要"发命令 + 核对实时值",在实时值真正到达之前什么都不要做。
Grbl_Esp32 的 VFD 基类把这个原则做成了完整的状态机,关键参数见 VFDSpindle.cpp:
VFD_RS485_POLL_RATE = 250 ms:命令之间的静默间隔(注释解释:按 Modbus 帧间 3.5 字符静默期估算,250ms 绰绰有余);RESPONSE_WAIT_MILLIS = 1000 ms:单条命令的响应超时;MAX_RETRIES = 5:失败重试上限,耗尽后标记变频器"无响应";VFD_RS485_QUEUE_SIZE = 10:命令队列深度。
vfd_cmd_task(L102-L293)的循环逻辑是:优先执行初始化序列(pollidx < 0),然后优先消费队列中排队的方向/速度命令,空闲时才做轮询;每条命令组帧后追加 CRC,最多重试 5 次,每次重试前vTaskDelay一个轮询周期。重试耗尽时:
- 普通命令:打印
Spindle RS485 Unresponsive,并把pollidx重置为 -1,让任务重新走一遍初始化序列; - 关键命令(执行作业期间的方向切换、零转速停机等):直接触发
mc_reset()并上报ExecAlarm::SpindleControl报警,见 VFDSpindle.cpp。
而"等到实际值匹配才放行"的实现位于set_state()(L445-L495):当supports_actual_rpm()为真(H2A 正是如此)时,固件置位_syncing标志,通信任务随即转为高频轮询get_current_rpm更新_sync_rpm;主线程则按mc_dwell(500)每 0.5 秒检查一次,容差为最大/最小转速差的 2.5%(且不低于 100 RPM),最多等 20 次(10 秒)。如果主轴始终到不了目标转速,报错Critical Spindle RS485 did not reach speed ...并触发SpindleControl报警——这正是文档所说"wedefinitelywant to wait and do nothing"的工程化版本。速度下发前还会先经set_rpm()(L529-L581)应用主轴转速倍率(sys.spindle_speed_ovr)并夹位到$31/$30设置的最小/最大转速区间,同时把_sync_rpm置为UINT32_MAX强制重新同步,防止上一次的速度值残留。
在 Grbl_Esp32 中启用 H2A 主轴
把协议文档落到固件配置上,需要三步:
- 选择主轴类型。
SpindleType枚举中包含H2A(见 Spindle.h),设置项名称表中注册为"H2A"(SettingsDefinitions.cpp),Spindle.cpp的选择逻辑在case SpindleType::H2A时把全局spindle指向H2A单例(Spindle.cpp)。机器定义文件中可通过#define SPINDLE_TYPE SpindleType::H2A设置默认值;该定义也是扩展设置Spindle/Type的初始值来源(SettingsDefinitions.cpp)。 - 定义三根 RS485 引脚宏
VFD_RS485_TXD_PIN/VFD_RS485_RXD_PIN/VFD_RS485_RTS_PIN,写法可参照 4axis_external_driver.h。从机地址默认0x01(VFDSpindle.cpp),与文档 F9.02 = 1 的设定一致;波特率则被 H2A 构造函数固定为 19200,无需再配。 - 按上表把变频器 F 参数写入并保存,尤其 F9.01 要配成 8E1 以匹配固件的 Even 校验,F9.05 = 0 保持非标准 Modbus 模式。
另有两点限制需要注意:get_pins_and_settings()中显式检查了激光模式,若LaserMode开启会打印VFD spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart并拒绝初始化(VFDSpindle.cpp);init()还会读取rpm_min/rpm_max设置作为速度夹位区间(VFDSpindle.cpp),而 H2A 的 100% 换算最终使用的是从 B0.05 读回的真实_max_rpm,因此$30(最大转速)应至少覆盖变频器的实际能力,否则高速请求会被夹位。
小结
H2ASpindle.md 用简洁的语言补齐了 H2A/H2B/H2C 变频器手册的协议缺口:3.3V 安全的 MAX485 接线、8 个 F 参数、小端 CRC 字节序,以及状态/转速/启停/调速的完整命令表。而 H2ASpindle.cpp 与 VFDSpindle.cpp 则展示了这些规则在 Grbl_Esp32 中的完整落地:19200-8E1 串口参数、百分比速度换算、上电读取 B0.05 建立_max_rpm、250ms 轮询 + 5 次重试 + CRC/地址/长度三重校验,以及"下发后轮询实际转速、10 秒内必须到达 ±2.5% 容差否则报警"的同步机制。理解这一文档与这两份源码的对应关系,就既能独立调试 P2A/P2B/P2C 变频器的 RS485 通信,也能快速判断"无响应"报警究竟卡在协议哪一环。
- 嵌入式
- 硬件开发
- 物联网
- 工业制造
【免费下载链接】Grbl_Esp32
A port of Grbl CNC Firmware for ESP32
相关推荐
FluidNC H100 变频器协议深度解析:RS485/Modbus 帧结构、指令集与固件实现全指南
FluidNC H100 变频器协议深度解析:RS485/Modbus 帧结构、指令集与固件实现全指南 导读 本文以 FluidNC 仓库中 H100Proto
嵌入式固件硬件开发智能硬件Grbl_Esp32 Dynamixel2 详解:把 Robotis 协议 2 舵机当作 CNC 轴电机的完整配置指南
Grbl_Esp32 Dynamixel2 详解:把 Robotis 协议 2 舵机当作 CNC 轴电机的完整配置指南 本篇指南以 Grbl_ESP32 仓库中
嵌入式硬件开发物联网工业制造FluidNC 中 H2A/H2B/H2C VFD 的 RS485 Modbus 协议接入指南:接线、参数配置与源码级命令解析
FluidNC 中 H2A/H2B/H2C VFD 的 RS485 Modbus 协议接入指南:接线、参数配置与源码级命令解析 H2A/H2B/H2C 系列变频
嵌入式固件硬件开发智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考