如果你做数控、做非标自动化,大概率会有这样一个困惑:为什么传统 HMI 总是越用越难受?
界面封闭、授权费贵、想加一个 AI 故障诊断功能,连基础 API 都不开放。供应商告诉你"这个要定制开发",报价单递过来,项目利润直接少掉一截。与此同时,树莓派加 LinuxCNC 的组合,已经让数控系统的控制层彻底开源了,从运动控制、IO 逻辑到脉冲输出都能自己掌控。但这两者之间其实还隔着一层:人机交互层怎么办?
如果 HMI 本身也开源、也软件化,同时把 AI 大模型作为"对话式诊断入口"嵌入进去,整套系统的玩法就会完全不一样。这也是本文想探讨并且实际搭建的方案:
用树莓派做底层运动控制平台,运行 LinuxCNC 实时控制;HMI 层独立成一个 Web 服务,不依赖任何商业组态软件;再把本地大模型接入 HMI,让操作员可以直接用自然语言问"主轴报错怎么办"、让 AI 给出诊断建议,甚至把 AI 输出的建议转成可确认执行的 MDI 命令。
换句话说:控制层用 LinuxCNC,交互层用 Web HMI,智能层用本地大模型。这套框架跑通之后,你就拥有了一套低成本、可扩展、带 AI 能力的数控 HMI 原型。
这篇文章会从痛点分析、架构设计、环境准备、LinuxCNC 配置、Web HMI 开发、AI 接入、完整示例、常见排错到工程建议,完整走一遍。不管你是树莓派爱好者、数控行业工程师,还是做工业软件的开发者,都不用照着抄,重点理解思路,然后换成自己的硬件和场景去落。
1. 这篇文章真正要解决的问题
先想清楚一个问题:我们到底为什么要做一个开源 HMI,而不是直接用现成的商业 HMI?
因为传统工控场景里,HMI 的价值不是"显示屏幕",而是"人对设备状态的感知入口"。而传统 HMI 软件最大的三个痛点恰恰卡在这里:
- 封闭:组态软件有自己的私有格式,想要换掉、升级、接入外部数据非常难。
- 定制成本高:界面改一个按钮,在开源 Web 框架里是分钟级改动,在商业 HMI 里可能要重新走一轮开发、测试、部署流程。
- AI 能力难以接入:市面上多数 HMI 不具备开放 API,即使有,也主要针对数据采集,而不是面向"语义理解"。
这些问题叠加在一起,导致一个很现实的局面:工厂里最需要智能诊断的数控设备,反而最不容易用上 AI。
这篇文章要做的,就是拆掉这堵墙。具体目标有三个:
- 让 LinuxCNC 的控制能力通过 Web HMI 暴露出来,包括读取坐标、主轴状态、发送 MDI 命令。
- 把本地大模型接入 HMI,让它可以基于 LinuxCNC 报警号、系统日志和运维知识库做故障诊断。
- 给 AI 加上执行边界——AI 只做分析和建议,真正的控制命令由操作员确认后发送,避免"大模型直接操作设备"这种不安全的设计。
这篇文章适合谁?
- 正在做树莓派数控项目,需要一个人机交互界面的开发者;
- 在做工业 HMI 选型,想评估"Web HMI + AI"路线是否可行的工程师;
- 对 LinuxCNC 感兴趣,想了解它除了 Axis 之外还能怎么接上层应用的嵌入式/自动化爱好者;
- 以及想找一个真实的"AI + 工业控制"落地案例的产品经理。
一句话总结:这是一套能从零跑通、能演示、能继续往生产方向扩展的开源 HMI 框架原型。
2. 三个关键概念:LinuxCNC、HMI、AI 在数控系统里的位置
在写代码之前,先把三个核心概念讲清楚。很多初学者容易把 LinuxCNC 当成一个"上位机软件",其实它的定位要复杂得多。
2.1 LinuxCNC 是什么
LinuxCNC 是一套基于 Linux 的开放式数控系统软件,前身是 EMC(Enhanced Machine Controller)。它不只做运动规划,还包括:
- 实时运动控制:通过实时内核(RT-PREEMPT 或 Xenomai)保证插补和脉冲输出的时序确定性;
- PLC 逻辑:通过 ClassicLadder 或 HAL 中的逻辑组件实现简单的梯形图、逻辑控制;
- 硬件抽象:通过 HAL(Hardware Abstraction Layer)把软件信号和物理引脚连接起来。
通俗理解:LinuxCNC 像是数控机床里的"大脑 + 脊髓",大脑负责 G 代码解释和轨迹规划,脊髓负责把运动指令通过引脚发到伺服驱动或步进驱动。
2.2 HMI 在数控系统里的位置
HMI(Human Machine Interface)是操作人员和数控系统之间的交互层。传统的数控 HMI 通常是一台嵌入式屏或者工控机上的组态软件,负责:
- 显示坐标、主轴转速、报警信息、加工状态;
- 提供按钮、输入框、程序列表等操作入口;
- 记录历史报警和操作日志。
在 LinuxCNC 系统里,最常用的 HMI 是 Axis 界面,一种基于 Tcl/Tk 的桌面 GUI。Axis 对单机用户够用,但如果你想做远程监控、多终端访问、或者在界面上嵌入 AI 对话功能,桌面 GUI 的方案就不够灵活了。
所以这套框架会把 HMI 单独提出来,做成一个 Web 应用。控制层不变,交互层独立,这是现代数控软件架构里的一个重要设计思路。
2.3 AI 在数控 HMI 里能做什么
AI 接入 HMI 不等于"放一个聊天框"。在工业场景里,真正有价值的三个方向是:
- 故障辅助诊断:操作员输入报警代码或描述现象,大模型结合运维知识库给出排查步骤。这是最容易落地、风险最低的方向。
- 自然语言转操作建议:操作员说"把 X 轴移动到 10 毫米",AI 解析成对应的 MDI 命令,显示给操作员确认后执行。中间有人为确认环节,安全可控。
- 基于历史数据的维护预测:通过分析伺服电流、温度、振动数据,预测丝杠、轴承的寿命。这个需要一定数据积累,适合作为后期方向。
本文重点实现前两个,第三个只作为后续思路扩展写在第 12 节。注意一个边界:AI 在工业控制里的角色是"副驾驶",不是"自动驾驶",控制权必须保留在操作员手里。
下面用一张表格对比三个概念的分工:
| 层次 | 核心软件 | 功能 | 类比 |
|---|---|---|---|
| 控制层 | LinuxCNC | 运动规划、插补、IO 控制、G 代码执行 | 数控系统的大脑 |
| 交互层 | Web HMI | 状态显示、操作入口、报警展示 | 数控系统的仪表盘 |
| 智能层 | 本地大模型 | 故障诊断、命令解析建议、知识问答 | 数控系统的 AI 副驾驶 |
3. 总体架构设计:树莓派如何把三层串起来
整个框架的架构可以概括为一句话:一个树莓派,承载三层服务。
[浏览器/移动端] | | (HTTP / WebSocket) v [Web HMI 服务] <-------> [AI 服务] | | | (linuxcnc python API) | (HTTP / Ollama API) v v [LinuxCNC 实时控制层] [本地大模型 + 知识库] | v [步进/伺服驱动、IO]不需要一级标题,文章直接从正文开始,这里的图用代码块或列表形式描述。
说明一下数据流:
- 下行控制链路:浏览器点击按钮 → Web HMI 服务 → LinuxCNC Python API → HAL 引脚 → 驱动;
- 上行状态链路:LinuxCNC 实时状态 → Web HMI 服务 → WebSocket 推送 → 浏览器实时刷新坐标;
- AI 诊断链路:操作员输入故障描述 → Web HMI 把上下文发给 AI 服务 → AI 结合知识库返回建议 → HMI 展示给操作员 → 操作员决定是否执行。
3.1 为什么选择树莓派作为硬件平台
树莓派不是唯一的选项,但在这个场景里它有明显优势:
- 成本低:一块树莓派 4B 或 5,加一张 TF 卡,几百元就能跑起整个框架;
- 生态成熟:Linux 系统的包管理机制,安装 Python、Node.js、Docker、Ollama 都非常方便;
- GPIO 可用:虽然实时性是弱项,但在低速步进控制、教学演示、原型验证场景里够用;
- 社区资料丰富:树莓派 + 数控、树莓派 + Linux 实时控制都有大量折腾记录。
它不适合大规模生产级数控改造。原因非常具体:树莓派的 GPIO 脉冲输出依赖软件定时器,在高频脉冲下会出现抖动;CPU 要同时处理实时控制、Web 服务和 AI 推理,压力很大。所以生产环境建议把 AI 服务拆到另一台机器,或者用外接运动控制卡。这一点在第 11 节还会展开。
3.2 为什么 AI 选本地大模型而不是云端 API
在工业环境里,"本地部署"不是偏好,而是需求。工厂车间网络往往封闭,数据不能随便出内网;操作员的使用场景要求低延迟;运维手册和报警代码属于内部知识,不适合传云端。所以选用 Ollama 部署本地大模型,既保证数据边界,也方便后续做成离线系统。
如果硬件条件允许,AI 服务也可以部署在局域网内的另一台 x86 机器上(带独显更好),树莓派只保留控制层和交互层。这个拆分方式对于实际项目非常重要。
4. 环境准备与前置条件
在开始配置之前,先把环境准备好。整个系统的软件栈如下:
- 硬件:树莓派 4B 或 5,推荐 4GB 以上内存;一张 32GB 以上 TF 卡;电源建议使用官方电源;
- 操作系统:Raspberry Pi OS(Bookworm 或更新版本,64 位);
- LinuxCNC:请以官方发布版本为准,树莓派等 ARM 平台可关注社区或官方提供的安装包/镜像;
- AI 推理:Ollama,在树莓派上优先选择支持 ARM 架构的模型(如 qwen2.5:3b、llama3.2:3b);
- 后端框架:Python 3 + Flask/FastAPI;
- 前端:HTML + JavaScript,不依赖大型前端框架,方便新手理解。
4.1 系统初始化
烧录系统后,开机进入终端,先更新系统并安装基础工具:
sudo apt update && sudo apt upgrade -y sudo apt install -y python3 python3-pip git curl4.2 安装 LinuxCNC
LinuxCNC 在 x86 平台上有成熟的一键安装脚本。树莓派等 ARM 平台建议先查询 LinuxCNC 官方文档或社区是否有对应发行包。
如果安装包不可用,另一个常见思路是使用 LinuxCNC 的派生项目 Machinekit,它专门针对 ARM 平台做过移植。不过要注意,两个项目的配置语法和模块名有差异,本文以 LinuxCNC 官方用法为主线,ARM 平台的具体安装过程请以实际文档为准。
装完之后,验证一下命令是否可用:
linuxcnc --version如果终端能正常输出版本信息,说明 LinuxCNC 已经进入系统路径。
4.3 安装 Ollama 与模型
Ollama 的安装脚本:
curl -fsSL https://ollama.com/install.sh | sh拉取一个适合树莓派资源的小模型:
ollama pull qwen2.5:3b启动 Ollama 服务:
ollama serve验证模型可以正常对话:
ollama run qwen2.5:3b "请用一句话说明什么是 G 代码"在树莓派上跑 7B 模型会比较吃力,3B 级别是性能和效果之间的折中。
4.4 安装 Python 依赖
Web HMI 服务需要的 Python 包:
pip3 install flask flask-socketio requests如果 LinuxCNC 的 Python 模块没有安装到系统 Python 路径,需要检查安装方式。通常安装 LinuxCNC 时会自带linuxcnc这个 Python 模块,位置可能在/usr/lib/python3/dist-packages/或者安装目录下。
5. LinuxCNC 基础配置:INI 与 HAL
LinuxCNC 的核心配置由两类文件组成:INI 文件定义机床的基本参数和启动设置,HAL 文件定义硬件信号之间的连接关系。理解这两个文件,是后续一切开发的前提。
5.1 INI 文件的核心结构
INI 文件用段落(Section)组织,常见的段落包括:
[EMC]:基本名称和调试级别;[DISPLAY]:选择显示界面;[TRAJ]:轴数量、最大速度、最大加速度;[AXIS_x]:每个轴的参数;[EMCMOT]:运动控制模块参数;[HAL]:指定 HAL 文件的路径。
下面是一个最小示例,三轴雕刻机配置:
# 文件路径:~/linuxcnc/configs/pi_cnc/mill.ini [EMC] MACHINE = Pi CNC Demo DEBUG = 0 [DISPLAY] DISPLAY = axis CYCLE_TIME = 0.100 [TRAJ] AXES = 3 COORDINATES = X Y Z MAX_LINEAR_VELOCITY = 50.0 DEFAULT_LINEAR_VELOCITY = 20.0 [EMCMOT] EMCMOT = motmod [HAL] HALFILE = mill.hal [AXIS_0] TYPE = LINEAR MAX_VELOCITY = 50.0 MAX_ACCELERATION = 100.0 MIN_LIMIT = -100.0 MAX_LIMIT = 100.0 [AXIS_1] TYPE = LINEAR MAX_VELOCITY = 50.0 MAX_ACCELERATION = 100.0 MIN_LIMIT = -100.0 MAX_LIMIT = 100.0 [AXIS_2] TYPE = LINEAR MAX_VELOCITY = 50.0 MAX_ACCELERATION = 100.0 MIN_LIMIT = -50.0 MAX_LIMIT = 50.0这段配置的核心含义是:系统包含 X、Y、Z 三个直线轴,最大速度 50 毫米每秒,每个轴都有软限位。注意速度单位取决于你的运动控制配置,在实际项目中需要结合电机步距角、驱动器细分数和丝杠导程重新计算。
5.2 HAL 文件的核心结构
HAL 是 LinuxCNC 的一大特色,它的设计理念类似于硬件的"连线"。每个组件有引脚(pin),信号(signal)负责把组件的引脚连起来。比如把运动控制器的步进输出引脚连到并口或 GPIO 引脚上:
# 文件路径:~/linuxcnc/configs/pi_cnc/mill.hal loadrt trivkins loadrt stepgen step_type=0,0,0 loadrt hal_parport cfg="0x378 out" setp stepgen.0.position-scale 8000.0 setp stepgen.1.position-scale 8000.0 setp stepgen.2.position-scale 8000.0 addf stepgen.update-servo addf stepgen.make-pulses base-thread addf parport.0.write base-thread net x-step stepgen.0.step parport.0.pin-02-out net x-dir stepgen.0.dir parport.0.pin-03-out这里position-scale的值表示"多少个脉冲对应 1 毫米",取决于你的驱动细分数和丝杠导程。上面只是一个连接示意,不同硬件对应的驱动模块名称差异很大。
5.3 用文本方式启动 LinuxCNC
LinuxCNC 自带图形启动器,但为了后续通过 HMI 自动拉起来,可以用命令行方式直接启动:
linuxcnc ~/linuxcnc/configs/pi_cnc/mill.ini启动后,你应该能看到 Axis 图形界面。如果能看到坐标界面,说明 INI 和 HAL 至少语法上没有大问题。
这里就出现了一个很重要的判断:LinuxCNC 能正常启动,只代表配置语法正确,不代表硬件链路正确。第一次接电机时,一定要先手动转动电机轴,观察坐标变化方向,再测试使能、限位和急停。
6. 构建 Web HMI 服务:让控制能力浮出水面
LinuxCNC 本身提供了 Python API,这是 Web HMI 能接上去的关键。官方提供三个主要对象:
linuxcnc.command():发送运动命令、切换模式、执行 MDI;linuxcnc.stat():读取状态,如坐标、主轴转速、报警;linuxcnc.error_channel():读取错误消息。
6.1 后端服务的核心代码
下面用一个 Flask 应用,实现两个基本 API:读取坐标和发送 MDI 命令。
# 文件路径:hmi_service/app.py from flask import Flask, request, jsonify import linuxcnc app = Flask(__name__) c = linuxcnc.command() s = linuxcnc.stat() def get_position(): """读取当前机械坐标和机床坐标""" s.poll() return { "joint_0": s.joint_position[0], "joint_1": s.joint_position[1], "joint_2": s.joint_position[2], } @app.route("/api/status") def status(): """HMI 轮询或 WebSocket 推送的状态接口""" s.poll() return jsonify({ "position": get_position(), "state": s.state, "exec_state": s.exec_state, "task_mode": s.task_mode, "spindle_speed": s.spindle_speed, "current_vel": s.current_vel, }) @app.route("/api/mdi", methods=["POST"]) def send_mdi(): """发送 MDI 命令,例如 G0 X10 Y10""" data = request.get_json(force=True) mdi_cmd = data.get("cmd", "").strip() if not mdi_cmd: return jsonify({"error": "cmd is required"}), 400 # 切换到 MDI 模式并执行 c.mode(linuxcnc.MODE_MDI) c.wait_for_completion() c.mdi(mdi_cmd) c.wait_for_completion() return jsonify({"ok": True, "cmd": mdi_cmd}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)这段代码完成了两件最核心的事:把 LinuxCNC 状态变成 HTTP JSON 接口,把 MDI 命令变成可调用的 HTTP API。有了这两个能力,前端界面的自由度就完全打开了。
6.2 前端页面:一个能跑的最小 HMI
为了让示例完整,下面给一个极简 HTML 页面,包含状态显示、坐标显示、MDI 输入和 AI 诊断入口。
<!-- 文件路径:hmi_service/templates/index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Pi CNC HMI</title> <style> body { font-family: system-ui, sans-serif; margin: 2rem; } .card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin-bottom: 1rem; } button { padding: 0.5rem 1rem; cursor: pointer; } </style> </head> <body> <h1>树莓派数控 HMI</h1> <div class="card"> <h2>状态</h2> <div id="state">---</div> <div id="pos">X: --- Y: --- Z: ---</div> <div id="spindle">主轴转速: ---</div> </div> <div class="card"> <h2>MDI 命令</h2> <input type="text" id="mdi_cmd" value="G0 X5 Y5" size="40"> <button onclick="sendMdi()">发送</button> </div> <div class="card"> <h2>AI 辅助诊断</h2> <input type="text" id="ask" placeholder="例如:X轴报警,可能是哪些原因?" size="60"> <button onclick="askAi()">提问</button> <pre id="answer">等待提问...</pre> </div> <script> async function refreshStatus() { const resp = await fetch('/api/status'); const data = await resp.json(); const p = data.position; document.getElementById('state').innerText = '状态: ' + data.state; document.getElementById('pos').innerText = 'X: ' + p.joint_0.toFixed(3) + ' Y: ' + p.joint_1.toFixed(3) + ' Z: ' + p.joint_2.toFixed(3); document.getElementById('spindle').innerText = '主轴转速: ' + data.spindle_speed; } async function sendMdi() { const cmd = document.getElementById('mdi_cmd').value; const resp = await fetch('/api/mdi', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({cmd: cmd}) }); const data = await resp.json(); alert('已发送: ' + data.cmd); } async function askAi() { const question = document.getElementById('ask').value; const resp = await fetch('/api/ai', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({question: question}) }); const data = await resp.json(); document.getElementById('answer').innerText = data.answer || '无回答'; } setInterval(refreshStatus, 1000); </script> </body> </html>前端用轮询方式每秒刷新一次状态,对小型原型完全够用。如果想做更流畅的实时体验,可以把轮询改成 WebSocket,LinuxCNC 状态变化时主动推送,但这不是核心重点。
7. 接入 AI:本地大模型与知识库
HMI 页面已经能显示状态、发命令了,接下来接入 AI。这里采用一个务实的思路:AI 服务单独作为一层,不直接和 LinuxCNC 对话,HMI 是唯一的中间人。
7.1 为什么需要知识库
直接让大模型回答数控问题,效果很可能不理想。原因是大模型训练数据里虽然有机械加工、G 代码方面的通用知识,但它并不知道你的 LinuxCNC 配置、你用的报警代码含义、你的机床丝杠规格。要想得到真正可用的诊断建议,需要把运维手册、报警代码表、历史维修记录整理成知识库,让 AI 在回答时先检索相关内容。
这个做法在技术上叫 RAG(Retrieval-Augmented Generation,检索增强生成)。在原型阶段,不一定要用复杂的向量数据库,可以先把知识库文本按章节切成片段,根据关键词简单匹配后拼进 Prompt。
7.2 AI 后端接口实现
下面是一个简单的 AI 服务接口,调用 Ollama 的/api/generate接口,并把系统提示词设为"数控运维专家":
# 文件路径:hmi_service/ai_service.py import requests import json from flask import Blueprint, request, jsonify ai_bp = Blueprint("ai", __name__, url_prefix="/api") OLLAMA_URL = "http://localhost:11434/api/generate" MODEL_NAME = "qwen2.5:3b" SYSTEM_PROMPT = """你是一名数控机床运维专家。请根据用户提供的故障现象或报警代码,给出: 1. 可能的原因 2. 排查步骤 3. 处理建议 注意: - 如果问题描述不明确,先请用户补充信息; - 不要直接生成可能造成设备损坏的操作建议; - 涉及安全操作时,提醒用户确认急停和限位状态。 """ def search_knowledge_base(question): """从本地知识库检索相关片段。原型阶段用简单的关键词匹配。""" # 实际项目中可以替换为向量数据库检索 db_text = "" try: with open("knowledge_base.txt", "r", encoding="utf-8") as f: db_text = f.read() except FileNotFoundError: return "" paragraphs = db_text.split("\n\n") matched = [] for para in paragraphs: if any(keyword in para for keyword in question.split()): matched.append(para) return "\n\n".join(matched[:3]) @ai_bp.route("/ai", methods=["POST"]) def ask_ai(): data = request.get_json(force=True) question = data.get("question", "") context = search_knowledge_base(question) prompt = SYSTEM_PROMPT if context: prompt += f"\n\n以下是运维知识库中的相关内容:\n{context}" payload = { "model": MODEL_NAME, "prompt": prompt + f"\n\n用户的问题:{question}\n\n请使用中文回答。", "stream": False, } try: resp = requests.post(OLLAMA_URL, json=payload, timeout=60) result = resp.json() answer = result.get("response", "AI 服务无返回") except Exception as e: answer = f"调用 AI 服务失败: {str(e)}" return jsonify({"answer": answer})这段代码里,知识库还是一个非常朴素的关键词匹配实现。生产环境可以换成 Chroma、Milvus 或 Elasticsearch 做向量检索,效果会更好。框架层面只需要替换search_knowledge_base函数内部实现。
7.3 AI 结果如何转化成可执行操作
很多读者会问:AI 回答了故障原因,然后呢?它能不能自己去执行 MDI 命令?
这里有一个非常关键的安全设计判断:不建议让 AI 直接执行命令。更稳妥的方式是"AI 转成建议命令 → 人工确认 → 执行"。比如操作员问"把主轴转速设为 1000",AI 输出:
建议执行的 MDI 命令:M3 S1000随后 HMI 弹出一条确认框:"AI 建议执行 M3 S1000,是否确认?",操作员确认后才真正调用send_mdi接口。这套流程叫"human-in-the-loop",是 AI 进入工业控制领域的基本安全准则。
8. 完整示例:把 HMI 和 AI 串起来
前面各节分别实现了控制层配置、HMI 后端、前端页面、AI 服务。这一节把它们组合成一个完整的项目结构,并给出启动流程。
8.1 项目目录结构
hmi_service/ ├── app.py # Flask 主应用 ├── ai_service.py # AI 路由模块 ├── knowledge_base.txt # 运维知识库 ├── templates/ │ └── index.html # HMI 前端页面 └── requirements.txt # Python 依赖8.2 修改主应用注册 AI 路由
在app.py中注册 AI 服务蓝图:
# 文件路径:hmi_service/app.py(补充部分) from ai_service import ai_bp app.register_blueprint(ai_bp)8.3 运维知识库示例
知识库文件的内容格式可以是纯文本,按段落组织,每个段落尽量围绕一个主题:
铝件加工时主轴高速运转,出现共振现象。 可能原因:1. 主轴转速与刀具齿数匹配不当;2. 刀柄伸出过长;3. 工件装夹刚性不足。 排查建议:先降低转速,检查刀具伸出长度,再检查装夹是否牢固。 伺服驱动器报警:过流。 可能原因:1. 电机线短路;2. 驱动器参数错误;3. 机械卡死。 排查建议:断开电机,测量电机线相间电阻;检查机械负载是否卡滞;查看驱动器参数是否被误修改。8.4 启动流程
按下面顺序启动三个服务:
# 终端 1:启动 LinuxCNC linuxcnc ~/linuxcnc/configs/pi_cnc/mill.ini # 终端 2:启动 Ollama ollama serve # 终端 3:启动 Web HMI cd hmi_service python3 app.py启动完成后,在浏览器访问:
http://<树莓派IP>:5000如果能在浏览器看到状态页面,并且坐标值能随 LinuxCNC 状态变化,说明整条链路已经打通。
9. 运行结果与效果验证
框架搭建完成不等于项目完成,必须做一轮完整的验证。我建议按照下面的顺序逐项确认。
9.1 验证 HMI 能读取实时状态
在 LinuxCNC Axis 界面里手动移动 X 轴,观察浏览器 HMI 页面中的 X 坐标是否同步变化。
判断标准:
- 坐标值在 1 秒内刷新;
- 数值变化方向与手动移动方向一致;
- 无报错信息出现在 Flask 终端。
如果刷新慢,先检查树莓派的负载;如果方向相反,需要检查 HAL 中 stepgen 的方向引脚逻辑,不要在软件里强行改符号。
9.2 验证 MDI 命令链路
在 HMI 页面的 MDI 输入框输入:
G0 Z5点击发送,观察 Axis 界面中的 Z 轴是否运动到 5 毫米位置。
注意:执行前请确保机床处于"机器上电"且"急停复位"状态,否则任务没有进入执行队列。
9.3 验证 AI 问答
在 AI 诊断输入框输入:
X 轴报警,可能是哪些原因?判断标准:
- AI 在 10 到 30 秒内给出回答;
- 回答包含可能的故障原因和排查步骤;
- 回答中没有给出明显危险的操作建议。
如果模型回答质量不佳,可以换更大的模型,或者优化知识库内容。树莓派上首选 3B 级别的量化模型,资源上更现实。
10. 常见问题与排查思路
在实际搭建过程中,几乎每个环节都会遇到问题。下面把最常见的场景列成表格,方便按图索骥。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| LinuxCNC 启动失败 | INI 文件路径错误或 HAL 模块不匹配 | 查看启动日志,确认 HAL 文件路径 | 逐行检查 INI 中的 HALFILE 路径,确认驱动模块存在 |
| Axis 界面里坐标无响应 | 未完成机器使能,或者急停生效 | 检查 Axis 界面左下角状态 | 先点"机器上电",再复位急停 |
| 步进电机不转 | HAL 引脚连接错误或驱动板电源问题 | 在 LinuxCNC 的 HAL 监控面板查看 step 引脚是否有脉冲 | 确认 stepgen 输出连接到正确引脚,检查驱动板供电 |
| 树莓派 GPIO 无法访问 | 权限不足或设备树覆盖未启用 | 查看 GPIO 设备节点是否存在 | 使用sudo启动测试,并配置 udev 规则 |
| HMI 页面无法打开 | Flask 服务未启动或端口被占用 | 在树莓派本机访问localhost:5000 | 查看 Flask 日志,杀掉占用端口的进程 |
| 坐标刷新很慢 | 浏览器轮询频率太高导致树莓派负载过高 | 观察top命令输出 | 把轮询时间从 1 秒改为 2 到 3 秒,或改用 WebSocket |
| AI 回答速度很慢 | 模型参数过大,或 CPU 推理能力不足 | 查看 Ollama 日志,观察模型加载时间 | 换成 3B 以下量化模型,或把 AI 服务部署到局域网 x86 机器 |
| AI 回答经常不靠谱 | 知识库内容不足或 Prompt 设计不清晰 | 检查知识库片段能否被检索到 | 扩充知识库,优化检索方式,测试不同 Prompt 模板 |
这里要特别强调一下 GPIO 和驱动板的安全问题。树莓派的 GPIO 引脚是 3.3V 电平,直接接到 5V 步进驱动板可能损坏 GPIO。必须通过光耦隔离模块,或者使用专门的 CNC 扩展板。我不建议直接用杜邦线把 GPIO 拉出来接驱动器,这个操作风险比较高。
11. 最佳实践与工程建议
原型跑通之后,如果想往真实项目上推进,下面这几条建议值得认真对待。
11.1 安全边界必须前置设计
AI 接入工业控制最容易犯的错误,是把演示 demo 的设计直接搬进生产环境。工业系统里任何一条错误的运动指令都可能造成撞机、断刀甚至人身伤害。因此,在生产环境中必须做到:
- AI 永不直接执行命令,所有 AI 生成的 MDI 命令必须经过操作员确认;
- MDI 命令白名单化:在 HMI 后端限制可执行的命令范围,比如只允许
G0、G1、M3、M5等安全指令; - 急停和限位信号优先于所有软件逻辑,这是硬件层面的事,不能依赖软件层做兜底。
11.2 把 AI 服务拆出去
树莓派的 CPU 要跑 LinuxCNC 实时任务,还要跑 Web 服务和 AI 推理,负载会非常紧张。更合理的生产架构是:
- 树莓派只跑 LinuxCNC 和 HMI 后端;
- AI 服务部署在局域网内一台 x86 机器上,通过 HTTP 接口调用;
- 知识库检索也放到 AI 那台机器上,避免树莓派 IO 压力过高。
这样也方便后续升级 GPU,把大模型从 3B 升级到 7B 甚至更大。
11.3 日志与审计
AI 诊断是一回事,AI 建议背后的责任边界是另一回事。生产环境中,每条 AI 生成的建议和操作员是否确认执行,都应该记录日志。格式建议包含:
- 时间戳;
- 用户输入的问题;
- AI 的完整输出;
- 确认状态(已确认 / 已忽略);
- 如果执行了,执行的 MDI 命令和最终结果。
这既能帮助优化 Prompt 和知识库,也能在出问题时快速复盘。
11.4 从原型到生产的演进路径
基于这套框架,你可以逐步叠加更多能力:
- 把 LinuxCNC 从 GPIO 脉冲方案换成 Mesa 运动控制卡,提升脉冲稳定性和轴数;
- 在 HMI 前端加入远程监控、加工进度曲线、历史报警图表;
- 引入真正的向量数据库,把知识库规模做大;
- 在树莓派上接入摄像头模块,做基于视觉的加工状态检测;
- 把 AI 故障诊断升级为基于历史数据的预测性维护。
12. 总结与后续学习方向
这套框架跑通下来,你会发现真正重要的不是"Python 代码怎么写",而是你怎么理解数控系统各层之间的边界。LinuxCNC 管好实时运动控制,Web HMI 管好状态展示和操作入口,AI 管好知识理解和辅助决策。每一层各司其职,再通过清晰的 API 连接,整个系统就是可维护、可扩展的。
本文给出的也不只是一个 demo,而是一种思路:开源控制层 + 独立交互层 + 本地智能层。这个组合非常适合树莓派爱好者、数控行业工程师做技术预研,也适合作为公司内部"AI 改造传统数控"的验证原型。
如果你想继续深入,建议按这个顺序学习:
- 把 LinuxCNC 文档完整过一遍,重点看 HAL 和 INI 的官方说明,理解运动控制的数据流动;
- 读一读 LinuxCNC 的 Python 模块源码,知道除了坐标和 MDI,还能读取哪些状态字段;
- 把 RAG 知识库升级成向量检索,收集更多报警代码和维修经验,让 AI 的诊断能力真正可用;
- 尝试在 HMI 里接入摄像头模块,把机器视觉和 AI 诊断结合起来,向"智能数控终端"的方向走。
最后提醒一句:所有涉及真实设备运转的测试,务必在安全环境、合法授权、有急停保护的前提下进行。先把框架放在教学实验台上跑稳,再谈改造真正的机床。技术和安全,这两件事永远要一起考虑。