一、先建立认知框架
这份岗位本质上是三件事的交叉:
半导体行业知识 Qt/C++ 工程能力 软件工程规范
(GMP、SEMI、工艺) × (实时、多线程、图形) × (测试、CI/CD、文档)
优先级排序(新入职第一个月):
- 先摸清设备硬件------腔室结构、传感器类型、通信协议
- 再读懂现有代码架构------不要急于动手改
- 最后才开始写新功能
二、核心模块逐项拆解
模块 1:HMI/UI 设计开发
设计原则(符合 SEMI 标准)
半导体界面不是普通软件,有严格的行业规范:
| 规范要求 | 具体实现 |
|---|---|
| 操作确认二次验证 | 危险操作(手动开阀、急停)必须弹框二次确认 |
| 颜色语义统一 | 绿=正常运行,黄=警告,红=报警/故障,灰=离线 |
| 关键数值大字显示 | 腔室压力、温度 PV/SP 字号不低于 16pt |
| 操作可撤销/有记录 | 每次手动操作写审计日志 |
| 洁净室手套操作 | 按钮最小尺寸 40×40px,间距足够,避免误触 |
cpp
// 实际代码示例:危险操作确认对话框
bool ConfirmDangerousOp(QWidget* parent, const QString& opName) {
QMessageBox dlg(parent);
dlg.setWindowTitle("操作确认");
dlg.setText(QString("即将执行:<b>%1</b>").arg(opName));
dlg.setInformativeText("此操作不可撤销,确认继续?");
dlg.setIcon(QMessageBox::Warning);
dlg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
dlg.setDefaultButton(QMessageBox::No); // 默认选"否",防误操作
// 写操作日志
AuditLogger::instance().log(
AuditEvent::DangerousOpConfirm, opName,
UserManager::currentUser()
);
return dlg.exec() == QMessageBox::Yes;
}
界面架构:主窗口布局策略
bash
┌──────────────────────────────────────────────────────┐
│ 标题栏:设备名 | 当前用户 | 系统时间 | 网络状态 │
├────────────┬─────────────────────────┬───────────────┤
│ │ │ │
│ 导航侧栏 │ 主工作区(可切换页) │ 报警侧边栏 │
│ -------- │ ─ 腔室总览 │ 实时报警列表 │
│ Overview │ ─ 工艺监控 │ ─────────── │
│ Process │ ─ 配方编辑 │ 当前报警数:3 │
│ Recipe │ ─ 历史数据 │ ○ TEMP HIGH │
│ Alarm │ ─ 系统设置 │ ○ PRESS LOW │
│ History │ │ │
│ Settings │ │ │
├────────────┴─────────────────────────┴───────────────┤
│ 状态栏:设备状态 | 当前配方 | 运行计时 | 下一步预告 │
└──────────────────────────────────────────────────────┘
cpp
// QDockWidget 实现可停靠的报警面板
class MainWindow : public QMainWindow {
void setupLayout() {
// 报警面板可停靠到右侧,也可浮动
QDockWidget* alarmDock = new QDockWidget("实时报警", this);
alarmDock->setWidget(new AlarmPanel(this));
alarmDock->setFeatures(QDockWidget::DockWidgetMovable);
addDockWidget(Qt::RightDockWidgetArea, alarmDock);
// 中央区用 QStackedWidget 切换页面(比 Tab 更灵活)
m_stack = new QStackedWidget(this);
m_stack->addWidget(new ChamberOverviewPage());
m_stack->addWidget(new ProcessMonitorPage());
m_stack->addWidget(new RecipeEditorPage());
setCentralWidget(m_stack);
}
};
模块 2:C++ Qt 高性能实时系统
多线程架构(最重要、最容易出问题)
cpp
// 正确的线程模型:Worker Object Pattern
// 永远不要继承 QThread,除非你知道自己在做什么
class CommWorker : public QObject {
Q_OBJECT
public:
explicit CommWorker(QObject* parent = nullptr);
public slots:
void initialize(); // 在工作线程中初始化
void pollDeviceData(); // 定时轮询(由 QTimer 触发)
void sendCommand(Command cmd);
signals:
void dataReady(SensorPacket packet);
void connectionLost();
void errorOccurred(CommError err);
private:
QModbusTcpClient* m_modbus{nullptr};
QTimer* m_pollTimer{nullptr};
static constexpr int POLL_INTERVAL_MS = 100;
};
// 主线程启动工作线程
void MainWindow::startCommThread() {
QThread* thread = new QThread(this);
CommWorker* worker = new CommWorker();
worker->moveToThread(thread);
// 线程启动时初始化
connect(thread, &QThread::started, worker, &CommWorker::initialize);
// 数据回来更新 UI(QueuedConnection 自动处理跨线程)
connect(worker, &CommWorker::dataReady,
this, &MainWindow::updateDisplay,
Qt::QueuedConnection);
// 清理
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
thread->start();
}
实时数据可视化:工艺曲线
cpp
// QCustomPlot 实战配置(比 Qt Charts 性能高 3-5 倍)
class ProcessCurveWidget : public QCustomPlot {
public:
explicit ProcessCurveWidget(QWidget* parent = nullptr) {
// 添加三条曲线:温度、压力、RF功率
setupCurve(0, "温度 (°C)", Qt::red, yAxis);
setupCurve(1, "压力 (mTorr)", Qt::blue, yAxis2);
setupCurve(2, "RF功率 (W)", Qt::green, yAxis2);
// X轴为相对时间(秒),Y轴双轴
xAxis->setLabel("时间 (s)");
xAxis2->setVisible(true);
yAxis2->setVisible(true);
// 启用缩放和拖拽
setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
// 定时刷新(与数据采集解耦)
m_refreshTimer = new QTimer(this);
connect(m_refreshTimer, &QTimer::timeout,
this, [this]{ replot(QCustomPlot::rpQueuedReplot); });
m_refreshTimer->start(200); // UI 5fps 刷新即可
}
// 数据入口:可从任意线程安全调用
void appendData(double t, double temp, double press, double rf) {
QMutexLocker lock(&m_mutex);
m_tempData.append({t, temp});
m_pressData.append({t, press});
m_rfData.append({t, rf});
// 滑动窗口:只保留最近 10 分钟数据
pruneOldData(t - 600.0);
}
private:
void setupCurve(int idx, const QString& name,
QColor color, QCPAxis* valueAxis) {
graph(addGraph(xAxis, valueAxis))->setName(name);
graph(idx)->setPen(QPen(color, 2));
graph(idx)->setAntialiasedFill(false); // 关闭填充,提升性能
}
QMutex m_mutex;
QVector<QCPGraphData> m_tempData, m_pressData, m_rfData;
QTimer* m_refreshTimer;
};
报警系统:分级 + 实时推送
cpp
// 报警引擎:独立线程持续监控
class AlarmEngine : public QObject {
Q_OBJECT
public:
struct AlarmRule {
QString tag; // 如 "CHAMBER_A_TEMP"
double highLimit;
double lowLimit;
AlarmLevel level; // Critical / Warning / Info
int delayMs; // 延时触发,避免瞬态误报
};
void evaluate(const SensorPacket& data) {
for (auto& rule : m_rules) {
double val = data.value(rule.tag);
bool triggered = (val > rule.highLimit || val < rule.lowLimit);
if (triggered && !m_activeAlarms.contains(rule.tag)) {
// 延时确认,防抖
m_pendingAlarms[rule.tag].startTimer(rule.delayMs);
} else if (!triggered && m_activeAlarms.contains(rule.tag)) {
clearAlarm(rule.tag);
}
}
}
signals:
void alarmTriggered(AlarmEntry alarm);
void alarmCleared(QString tag);
};
模块 3:PyQt 快速原型 + Python/C++ 集成
策略分工
bash
C++ Qt → 核心控制逻辑、设备通信、实时数据处理(性能敏感)
Python → 配方工具、数据分析脚本、快速原型、AI 功能集成
PyBind11 集成示例
cpp
// C++ 侧:暴露接口给 Python
// device_binding.cpp
#include <pybind11/pybind11.h>
#include "DeviceController.h"
namespace py = pybind11;
PYBIND11_MODULE(device_ctrl, m) {
py::class_<DeviceController>(m, "DeviceController")
.def(py::init<const std::string&>())
.def("connect", &DeviceController::connect)
.def("send_command", &DeviceController::sendCommand)
.def("read_sensor", &DeviceController::readSensor)
.def("get_alarm_list", &DeviceController::getAlarmList);
}
python
# Python 侧:用 PyQt 做数据分析工具界面
import sys
import device_ctrl # C++ 编译的 .pyd/.so
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
import pandas as pd
class RecipeAnalyzer(QMainWindow):
"""配方分析工具:Python 快速开发,调用 C++ 设备接口"""
def __init__(self):
super().__init__()
self.device = device_ctrl.DeviceController("192.168.1.100:502")
self.setup_ui()
def analyze_run(self, run_id: str):
"""获取历史数据并做统计分析"""
data = self.device.get_run_data(run_id)
df = pd.DataFrame(data)
# Cpk 工艺能力分析
mean = df['temp'].mean()
std = df['temp'].std()
usl, lsl = 350.0, 340.0
cpk = min((usl - mean), (mean - lsl)) / (3 * std)
self.cpk_label.setText(f"温度 Cpk = {cpk:.3f}")
self.plot_distribution(df['temp'])
模块 4:跨平台适配(Windows / Linux 嵌入式)
cpp
// 平台差异统一封装
class PlatformHelper {
public:
// 串口名:Windows=COM3,Linux=/dev/ttyUSB0
static QStringList availableSerialPorts() {
QStringList ports;
for (auto& info : QSerialPortInfo::availablePorts()) {
#ifdef Q_OS_WIN
if (info.portName().startsWith("COM"))
ports << info.portName();
#else
if (info.systemLocation().startsWith("/dev/tty"))
ports << info.systemLocation();
#endif
}
return ports;
}
// 高精度定时器
static qint64 highResTimestamp() {
#ifdef Q_OS_WIN
LARGE_INTEGER freq, count;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&count);
return count.QuadPart * 1000000LL / freq.QuadPart; // 微秒
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
#endif
}
};
// CMakeLists.txt 跨平台配置
if(WIN32)
target_link_libraries(SemitoolUI PRIVATE ws2_32 setupapi)
target_compile_definitions(SemitoolUI PRIVATE _WIN32_WINNT=0x0601)
elseif(UNIX)
target_link_libraries(SemitoolUI PRIVATE pthread rt)
# 嵌入式 Linux 可能需要裁剪
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os")
endif()
模块 5:权限管理与审计日志(GMP 合规核心)
cpp
// 三级权限体系(符合 FDA 21 CFR Part 11 / GMP 要求)
enum class UserLevel { Operator=1, Engineer=2, Admin=3 };
class AuditLogger {
public:
// 每条操作记录:时间戳、用户、操作类型、旧值、新值
void record(const AuditEntry& entry) {
QSqlQuery q(m_db);
q.prepare(R"(
INSERT INTO audit_log
(timestamp, user_id, action, target, old_val, new_val, ip_addr)
VALUES (?, ?, ?, ?, ?, ?, ?)
)");
q.addBindValue(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch());
q.addBindValue(entry.userId);
q.addBindValue(entry.action);
q.addBindValue(entry.target);
q.addBindValue(entry.oldValue);
q.addBindValue(entry.newValue);
q.addBindValue(entry.ipAddress);
q.exec();
// 审计日志不允许删除,只允许追加(GMP 要求)
}
// 导出 CSV 供质量审计
void exportToCSV(const QDateTime& from, const QDateTime& to,
const QString& path);
};
// UI 层:宏简化权限检查
#define REQUIRE_LEVEL(level) \
if (!UserManager::instance().checkLevel(level)) { \
QMessageBox::warning(this, "权限不足", \
"此操作需要 " + UserManager::levelName(level) + " 权限"); \
return; \
}
void RecipeEditorPage::onSaveClicked() {
REQUIRE_LEVEL(UserLevel::Engineer); // 工程师才能保存配方
// ... 保存逻辑
}
模块 6:测试体系
单元测试 → Qt Test / Google Test(每个模块独立测试)
集成测试 → 模拟设备(Mock)跑完整工艺流程
压力测试 → 72小时连续运行,监控内存泄漏、CPU占用
现场联调 → 带示波器、万用表,逐点核对传感器数值
cpp
// Qt Test 单元测试示例
class TestAlarmEngine : public QObject {
Q_OBJECT
private slots:
void testHighTempAlarm() {
AlarmEngine engine;
engine.addRule({"TEMP_A", 350.0, 200.0, AlarmLevel::Critical, 0});
QSignalSpy spy(&engine, &AlarmEngine::alarmTriggered);
// 注入超限数据
SensorPacket packet;
packet.insert("TEMP_A", 380.0); // 超过高限 350
engine.evaluate(packet);
QCOMPARE(spy.count(), 1); // 应触发 1 条报警
auto alarm = spy.at(0).at(0).value<AlarmEntry>();
QCOMPARE(alarm.level, AlarmLevel::Critical);
}
void testAlarmDebounce() {
// 测试防抖:100ms 内瞬态不应触发报警
AlarmEngine engine;
engine.addRule({"TEMP_A", 350.0, 200.0, AlarmLevel::Warning, 500});
// ... 验证延时逻辑
}
};
QTEST_MAIN(TestAlarmEngine)
模块 7:CI/CD 流程
yaml
TypeScript
# .gitlab-ci.yml 示例
stages:
- build
- test
- package
- deploy
build:
stage: build
script:
- cmake -B build -DCMAKE_BUILD_TYPE=Release
- cmake --build build -j$(nproc)
artifacts:
paths: [build/SemitoolUI]
unit_test:
stage: test
script:
- cd build && ctest --output-on-failure
coverage: '/TOTAL.*\s+(\d+%)$/'
memory_check:
stage: test
script:
# Valgrind 内存泄漏检测
- valgrind --leak-check=full ./build/test_alarm_engine
package:
stage: package
script:
- windeployqt build/SemitoolUI.exe # Windows 打包
only: [main, release/*]
模块 8:技术预研方向(加分项)
WebAssembly 嵌入式 UI
cpp
// Qt for WebAssembly:同一套代码编译到浏览器
// 用途:远程监控界面,无需安装客户端
// emcmake cmake .. -DCMAKE_TOOLCHAIN_FILE=<emsdk>/upstream/emscripten/cmake/...
// 实际场景:工程师在手机上看腔室状态
// 限制:WebSocket 替代 TCP,不能直接访问串口
AI 辅助操作界面
python
# 实际落地方向(近期可实现):
# 1. 异常预测:用历史数据训练简单模型,提前预警
# 2. 配方推荐:根据目标膜厚/折射率,AI 推荐初始配方参数
# 3. 自然语言日志搜索
import openai
def search_alarm_history(user_query: str, alarm_df: pd.DataFrame) -> str:
"""用大模型理解自然语言查询历史报警"""
context = alarm_df.tail(100).to_string()
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "你是半导体设备维护专家"},
{"role": "user", "content": f"报警记录:\n{context}\n\n问题:{user_query}"}
]
)
return response.choices[0].message.content
三、工作节奏与交付标准
一个功能模块的完整交付流程
TypeScript
需求评审(0.5天)
↓
界面原型(Figma/手绘,0.5天)→ 与硬件/工艺工程师确认
↓
编码实现(3-5天)
↓
单元测试(1天)
↓
代码 Review(提 MR,等同事审查)
↓
集成测试(联调,1-2天)
↓
文档更新(界面规格书、API 文档)
↓
合并主干 → CI 自动跑测试
↓
发版打包 → 移交测试工程师
文档标准(实际要写的)
| 文档类型 | 内容要点 |
|---|---|
| 界面规格书 | 每个控件的状态定义、交互逻辑、边界条件 |
| 操作手册 | 截图+步骤,给操作员用,不能有技术术语 |
| API 接口文档 | 类/函数签名、参数说明、返回值、异常处理 |
| 测试报告 | 用例、结果、覆盖率、已知缺陷列表 |
四、总结每个模块的关键
- UI 设计:先问工艺工程师怎么用,再画界面
- 多线程:通信永远在独立线程,UI 永远在主线程
- 实时曲线:数据采集和 UI 刷新频率要分开
- 报警系统:要有防抖延时,要有分级,要有确认机制
- 权限审计:每一条操作记录,永不删除
- 测试:72 小时不崩溃才算稳定
- CI/CD:合并代码前必须过自动化测试