简介:这份资源面向桌面级CNC雕刻机与3D打印机的开发者、嵌入式爱好者及运动控制学习者,提供基于Arduino生态的Grbl v1.1开源固件完整代码包,帮助读者理解G代码解析、步进电机驱动与运动规划的实现原理。压缩包共83个文件,约334KB,以C与头文件为核心(21个h、18个c),另含zbak备份、txt提交日志、md文档、csv错误码表、py流式发送脚本及ino示例,覆盖固件源码、硬件抽象层与配置工具链。目前已有219人学习下载。代码库采用模块化架构,运动控制、G代码解析与硬件抽象层相互独立,读者可据此研究直线与圆弧插补、自适应加速度控制、EEPROM参数持久化等机制,并动手修改运动规划算法或扩展新型电机驱动模块,是学习工业级运动控制与二次开发的实用参考。
1. 从一块 Arduino UNO 说起:grbl v1.1 到底解决了什么
很多人第一次接触 CNC,是从一台 3D 打印机或者一台小型雕刻机开始的。机器买回来,电机能转、主轴能响,但真正决定它能不能按你的意图走出精确轨迹的,是跑在控制板里的那套固件。grbl v1.1 就是在这个位置上被反复提起的名字——它是一个跑在 Arduino UNO/Nano 这类 8 位 AVR 芯片上的开源 CNC 运动控制固件,把 G 代码解析、加减速规划、步进脉冲生成这三件事塞进了一颗只有 2KB RAM 的单片机里。
它的价值不在于功能多,而在于“够用且稳”。你给它一段 G 代码,它负责把每个坐标点翻译成步进电机的脉冲时序,中间还要处理速度前瞻、圆弧插补、限位开关、急停这些事。适合谁?适合想自己搭一台桌面级 CNC、激光雕刻机、写字机,或者想搞明白运动控制底层逻辑的人。热词里常出现的 arduino智能小车、机器人运动控制、6轴机械臂运动控制,本质上和 grbl 要解决的是同一类问题:把抽象的运动指令变成电机轴上的确定动作。区别只是 grbl 把这件事做到了极致精简,让你能在最便宜的硬件上跑起来。
2. grbl v1.1 的运动控制内核:从 G 代码到脉冲的完整链路
2.1 为什么 8 位单片机也能做运动控制
很多人下意识觉得运动控制必须上 32 位芯片,其实关键看算法复杂度。grbl v1.1 的核心是一套“规划器 + 步进执行”的两级结构。规划器负责把 G 代码里的直线、圆弧拆成一个个小线段,然后对每个线段做加减速规划,算出进给速度和持续时间。步进执行层则用一个定时器中断,按规划好的速度曲线产生脉冲。
这里有个反直觉的点:grbl 并不是“边走边算”的。它在收到一段 G 代码后,会先把能规划的线段全部塞进一个环形缓冲区,然后由定时器中断按节奏取出来执行。缓冲区大小有限,所以 grbl 会做“前瞻”——提前看后面几段的速度能不能衔接上,避免每段都从零加速再减速到零。这个前瞻窗口默认是 16 段,可以通过$LOOKAHEAD参数调整。
Arduino UNO 的 ATmega328P 只有 16MHz 主频、2KB RAM,grbl 能跑起来靠的是极致的代码优化:定点数运算代替浮点、查表代替实时计算、中断里只做最必要的脉冲翻转。这也是为什么 grbl 的源码读起来有点“抠”,但每一行都有存在的理由。
2.2 把 grbl v1.1 烧进 Arduino UNO 的最小操作路径
先说硬件:一块 Arduino UNO(或者 Nano,引脚兼容),一根 USB 线,一台电脑。如果你用的是 CH340 串口芯片的国产板子,先装好驱动,否则 arduino ide下载后打不开串口的情况很常见。
烧录方式有两种:用 Arduino IDE 直接编译上传,或者用预编译的 .hex 文件通过 avrdude 烧录。推荐第一种,因为你能看到编译输出,出问题好排查。
# 方式一:Arduino IDE 编译上传 # 1. 打开 Arduino IDE,确保板子选 Arduino Uno,端口选对 # 2. 把 grbl 源码文件夹放到 Arduino 的 libraries 或直接打开 grbl.ino # 3. 点击“上传”,等待编译完成 # 注意:grbl 主文件是 grbl.ino,但依赖同目录下的 config.h、cpu_map.h 等# 方式二:avrdude 命令行烧录预编译 hex avrdude -c arduino -p m328p -P /dev/ttyUSB0 -b 115200 -U flash:w:grbl_v1.1.hex:i # -c arduino 指定编程器协议 # -p m328p 指定芯片型号 # -P 指定串口设备,Windows 下是 COM3 这类 # -b 115200 是 bootloader 波特率,不是串口通信波特率烧录完成后,打开 Arduino 串口监视器,波特率选 115200,你会看到 grbl 的版本号和Grbl 1.1 [‘$’ for help]的提示。这说明固件已经在跑了。
2.3 必调的 6 个参数:让电机真正按毫米走
grbl 烧进去只是第一步,真正让它适配你的机器,靠的是$参数。串口里输入$$可以查看所有参数,输入$x=值可以修改。下面这 6 个是必须调的:
| 参数 | 含义 | 典型值 | 怎么定 |
|---|---|---|---|
$100 | X 轴每毫米脉冲数 | 800 | 步进电机每圈脉冲数 ÷ 丝杆导程 |
$101 | Y 轴每毫米脉冲数 | 800 | 同上 |
$102 | Z 轴每毫米脉冲数 | 800 | 同上 |
$110 | X 轴最大速度 mm/min | 500 | 从低往高试,丢步就降 |
$120 | X 轴加速度 mm/s² | 100 | 太大容易丢步,太小走不快 |
$130 | X 轴最大行程 mm | 300 | 按实际机械行程填 |
计算$100的公式:步进电机步距角 1.8°,即 200 步/圈;驱动器如果设了 4 细分,就是 800 脉冲/圈;丝杆导程如果是 2mm,那每毫米就是 400 脉冲。这个数填错,机器走出来的尺寸就是错的,而且错得很有规律——走 100mm 实际走了 50mm,那就是脉冲数少了一半。
提示:调参数时先把速度设低,确认方向、行程都对,再逐步提速。方向反了就把对应轴的脉冲数改成负数,或者调接线。
2.4 用串口发 G 代码验证运动链路
参数调好后,不需要上位机也能验证。直接在串口监视器里发 G 代码:
$X ; 解锁机器(如果报了 ALARM) G91 ; 切换到相对坐标模式 G1 X10 F200 ; X 轴正向走 10mm,进给速度 200mm/min G1 X-10 F200 ; 走回来 G90 ; 切回绝对坐标如果电机按预期动了,说明从 G 代码解析到脉冲输出的整条链路是通的。这一步看着简单,但它是后面接上位机、跑复杂刀路的基础。很多人跳过这步直接上 grbl上位机,结果一出问题就分不清是固件、参数还是上位机的问题。
3. 避坑与排查:grbl 从能跑到能用的 5 个血泪教训
3.1 电机只响不转,或者转起来像在抖
现象:发 G 代码后电机发出嗡嗡声但不转,或者转得很卡。
原因:最常见的是脉冲数$100设得太大,导致脉冲频率超出了驱动器或电机的响应能力。另一个可能是加速度$120设得太高,电机扭矩跟不上。
解决:先把$100按公式算准,然后把$120降到 50 甚至更低,速度$110降到 200,确认能平滑转动后再往上加。如果还是抖,检查驱动器细分设置和电机接线是否有一相接触不良。
3.2 串口监视器里全是乱码
现象:打开串口监视器,显示的不是Grbl 1.1,而是一堆乱码。
原因:波特率不对。grbl v1.1 默认通信波特率是 115200,但有些老版本或者改过的固件可能是 9600。
解决:先把串口监视器波特率切到 115200。如果还是乱码,试试 9600。另外,Arduino IDE 的串口监视器有时候会占用端口导致上位机连不上,用之前先关掉。
3.3 一上电就报 ALARM,机器不动
现象:grbl 启动后直接进入 ALARM 状态,发任何运动指令都拒绝执行。
原因:grbl v1.1 默认开启了硬限位保护,如果限位开关没接或者接反了,它会认为机器已经撞到边界。另一个可能是$22参数设成了 1(硬限位使能),但实际没有开关。
解决:先发$X解锁,然后检查$22的值。如果没有限位开关,把$22设为 0。如果有开关,用?命令查看限位状态,确认触发逻辑是常开还是常闭,再调$5参数(限位开关反相)。
3.4 走圆弧时机器一顿一顿的
现象:跑 G2/G3 圆弧指令时,机器不是平滑走,而是一段一段地顿。
原因:grbl 把圆弧拆成很多小线段来逼近,如果$12(圆弧公差)设得太大,线段就少,走起来就顿;如果设得太小,线段太多,8 位单片机算不过来,反而会卡。
解决:$12默认是 0.002mm,一般不用改。如果顿得厉害,先检查$120加速度是不是太低,导致每段都要重新加速。适当提高加速度,同时确认$11(结点偏差)不要设得太离谱。
3.5 上位机连上了但一发指令就断
现象:用 grbl上位机(比如 Universal Gcode Sender、Candle)连上后,发指令没反应,或者发几条就断开。
原因:多半是串口缓冲区溢出或者流控没开。grbl 的接收缓冲区很小,上位机如果一次性猛发,就会丢数据。
解决:在上位机里开启“流控”或者“字符计数”模式,让上位机等 grbl 的ok回应再发下一条。另外,USB 线质量差也会导致断连,换一根带屏蔽的短線试试。
4. 从单轴到三轴联动:用 grbl 跑通一个完整刀路
4.1 用 Python 生成一段测试 G 代码
手工写 G 代码验证单轴可以,但验证三轴联动和圆弧插补,最好用脚本生成。下面这段 Python 生成一个矩形加一个圆的刀路:
# 生成一段测试用 G 代码:矩形 + 圆 # 坐标系原点设在工件左下角,Z 轴安全高度 5mm,下刀深度 -1mm lines = [] lines.append("G21 ; 单位设为毫米") lines.append("G90 ; 绝对坐标") lines.append("G0 Z5 ; 抬刀到安全高度") lines.append("G0 X0 Y0 ; 移动到起点") # 矩形:从 (0,0) 到 (50,0) 到 (50,30) 到 (0,30) 回 (0,0) lines.append("G1 Z-1 F100 ; 下刀") lines.append("G1 X50 Y0 F300") lines.append("G1 X50 Y30") lines.append("G1 X0 Y30") lines.append("G1 X0 Y0") lines.append("G0 Z5 ; 抬刀") # 圆:圆心 (25,15),半径 10 lines.append("G0 X35 Y15 ; 移动到圆起点") lines.append("G1 Z-1 F100") lines.append("G2 X35 Y15 I-10 J0 F300 ; 顺时针整圆") lines.append("G0 Z5") lines.append("G0 X0 Y0 ; 回原点") lines.append("M30 ; 程序结束") with open("test_path.gcode", "w") as f: f.write("\n".join(lines)) print("G 代码已生成,共", len(lines), "行")这段代码的逻辑很直白:先抬刀、定位,然后下刀走矩形,抬刀,再下刀走一个整圆。G2是顺时针圆弧,I和J是圆心相对于起点的偏移。整圆用G2 X35 Y15 I-10 J0表示从起点绕一圈回到起点。
4.2 用 grbl 跑这段代码并观察实际表现
把生成的test_path.gcode用上位机加载,或者直接通过串口逐行发送。建议先用空跑模式(不装刀具、Z 轴抬到安全高度)验证轨迹。
观察重点有三个:第一,矩形四个角是不是直角,如果变成圆角,说明加速度太低或者结点偏差太大;第二,圆是不是闭合的,如果起点和终点没接上,说明脉冲数有累积误差;第三,整体速度是不是均匀,如果某一段突然变慢,可能是前瞻缓冲区用完了。
如果条件允许,用 wokwi仿真平台arduino 这类在线仿真工具先跑一遍逻辑,能省不少烧板子的时间。仿真里可以接虚拟示波器看脉冲输出,对理解定时器中断的节奏很有帮助。
4.3 三轴联动的速度规划:为什么对角线走不快
grbl 的速度规划是按“合成速度”算的,也就是说 X 和 Y 同时动的时候,每个轴的实际速度是合成速度的分量。如果你设$110和$111都是 500mm/min,走 45 度对角线时,合成速度还是 500,但每个轴只分到约 353。这没问题。
问题出在走小线段逼近曲线时。每段都很短,grbl 的前瞻如果没算好,就会在每段末尾减速到零再重新加速,导致整体速度远低于设定值。这就是为什么同样的参数,走直线很快,走圆弧或者复杂曲线就慢得让人着急。
解决办法是适当提高$120和$121(加速度),让每段能在更短的距离内完成加减速。但加速度不能无限加,受电机扭矩和机械刚性限制。我一般会从 100 开始,每次加 50,直到听到电机声音变尖或者出现丢步,然后退回上一档。
5. 进阶技巧:用 ESP32 给 grbl 加一条无线通道
5.1 为什么考虑 ESP32 而不是继续用 USB
Arduino UNO 跑 grbl 很稳,但 USB 线连着电脑总归不方便。热词里 esp32 arduino、esp32使用arduino读取mpu6050传感器数据 这些方向说明很多人已经在用 ESP32 做物联网相关的扩展。把 ESP32 当成一个串口透传桥,接在 grbl 的 RX/TX 上,就能把 G 代码通过 WiFi 发过去。
具体做法:ESP32 跑一个 TCP Server,收到数据后原样转发到串口;同时把 grbl 的回应转发回 TCP 客户端。这样上位机可以通过网络连到 ESP32 的 IP,效果和 USB 直连一样。
// ESP32 作为 grbl 的串口转 WiFi 透传桥 #include <WiFi.h> const char* ssid = "your_ssid"; const char* password = "your_password"; WiFiServer server(23); // 用 23 端口,和 telnet 一致 HardwareSerial grblSerial(2); // 用 ESP32 的 UART2 void setup() { Serial.begin(115200); grblSerial.begin(115200, SERIAL_8N1, 16, 17); // RX=16, TX=17 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) delay(500); server.begin(); Serial.print("IP: "); Serial.println(WiFi.localIP()); } void loop() { WiFiClient client = server.available(); if (client) { while (client.connected()) { // 电脑发来的数据转发给 grbl if (client.available()) { grblSerial.write(client.read()); } // grbl 的回应转发回电脑 if (grblSerial.available()) { client.write(grblSerial.read()); } } client.stop(); } }这段代码的关键在grblSerial.begin(115200, SERIAL_8N1, 16, 17),它把 ESP32 的 GPIO16 设为 RX、GPIO17 设为 TX,和 grbl 的串口交叉连接。注意 ESP32 是 3.3V 电平,Arduino UNO 是 5V,直接连可能烧 ESP32 的 RX。稳妥做法是加一个电平转换模块,或者在 UNO 的 TX 和 ESP32 的 RX 之间串一个 1k 电阻再并一个 2k 到地做分压。
5.2 无线之后的新问题:延迟和丢包
WiFi 透传听起来美好,但实际用起来有两个坑。第一是延迟,TCP 本身有重传机制,但 grbl 的接收缓冲区很小,如果网络卡一下,上位机发的指令就可能被 grbl 当成连续流处理,导致解析错误。第二是丢包,虽然 TCP 不丢,但 ESP32 的串口缓冲区只有 256 字节,grbl 的回应如果来得太快,ESP32 来不及转发就会丢。
我的做法是在 ESP32 代码里加一个简单的流控:当串口缓冲区剩余空间小于 64 字节时,暂停从 TCP 读取,等 grbl 的ok回应后再继续。这需要在 ESP32 里解析 grbl 的回应内容,稍微复杂一点,但稳定性提升明显。
另一个习惯是:无线只用来发文件,不用来实时手动控制。手动对刀、试走这些操作还是用 USB 直连,响应更确定。无线适合把生成好的 G 代码整段发过去,然后让机器自己跑。
5.3 验证无线链路是否可靠的一个笨办法
不要一上来就跑复杂刀路。先发$I看版本,再发$$看参数,然后发G91 G1 X1 F100走 1mm,再走回来。重复 50 次,看有没有一次丢步或者报错。如果 50 次都稳,再跑矩形和圆。这个办法笨,但能帮你把“网络问题”和“运动控制问题”分开。
我自己的习惯是:每次改完接线或者换环境,都先跑一遍这个 50 次往返测试。听起来很傻,但它救过我至少三次——有一次是 WiFi 信道干扰导致偶发丢包,有一次是电平转换电阻虚焊,还有一次是 grbl 的$100被误改成了 80。这些问题如果不先隔离出来,跑复杂刀路时根本分不清是谁的锅。
希望帮到你。
本文还有配套的精品资源,点击获取