4. 软件架构设计:上位机烧录软件架构、通信协议选择与数据库设计

烧录产线的上位机软件,说白了就是整个产线的大脑。它得跟下位机(烧录器)通信,得管理烧录文件,还得记录每一颗芯片的命运。我见过不少产线,硬件堆得挺豪华,结果软件架构一塌糊涂,三天两头出问题。这一章,我就把这几年的经验掰开揉碎,跟你聊聊怎么搭一个靠谱的上位机软件。

4.1 上位机软件架构:别一上来就写代码

我个人习惯,先画架构图,再动手。一个典型的烧录上位机,我把它分成三层:

  • UI 层:操作界面,给产线工人用的。按钮要大,状态要直观,别整那些花里胡哨的动画。
  • 业务逻辑层:核心调度。比如什么时候开始烧录、烧录失败怎么处理、日志怎么记录。
  • 驱动/通信层:跟硬件打交道。封装好 USB、以太网或串口的收发细节。

为什么要分层?你想想看,如果哪天老板说“咱们把串口换成以太网”,你只需要改最下面那层,上面的逻辑完全不用动。我在一个项目里吃过这个亏——所有通信代码跟业务逻辑揉在一起,改一个协议差点把整个软件重写。

核心原则:高内聚,低耦合。 每一层只干自己的事,别越界。

下面这张图,是我常用的架构模型:

上位机烧录软件三层架构图 上位机烧录软件三层架构 UI 层(用户界面) 烧录进度条 | 状态指示灯 | 日志显示 | 参数配置 业务逻辑层(核心调度) 烧录流程控制 | 校验管理 | 日志记录 | 异常处理 驱动/通信层(硬件抽象) USB 驱动 | 以太网 Socket | 串口 API | 协议解析 各层之间通过定义好的接口通信,不直接调用内部函数

4.2 通信协议选择:USB、以太网还是串口?

这个问题,几乎每个客户都会问我。我的回答永远是:看场景。没有最好的协议,只有最合适的。

特性 USB 以太网 串口
传输速度 高速(480 Mbps) 百兆/千兆 低速(115200 bps 典型)
传输距离 5 米以内 100 米以上 15 米左右
抗干扰能力 中等 强(屏蔽线) 弱(易受干扰)
多设备支持 需 Hub,有限 天然支持,可组网 需多串口卡
开发复杂度 中等(需驱动) 低(Socket 编程) 低(直接读写)
典型场景 单工位、近距离 多工位、远距离 调试、低速设备

我的建议: 新项目优先考虑以太网。原因很简单——可扩展性好。今天你只有 4 个工位,明天扩到 40 个,换根交换机就行。USB 和串口扩起来就头疼了。

不过,如果你做的是便携式烧录器,或者现场只有 USB 口可用,那 USB 也是好选择。我记得有一次去客户现场,他们的产线工控机只有 USB 口,我临时把以太网方案改成 USB,折腾了两天。从那以后,我设计软件时都会把通信层抽象好,换协议就像换插件一样。

4.2.1 通信协议设计:别裸传数据

不管用哪种物理层,应用层协议都得自己定义。我见过最坑的做法——直接把二进制文件流发过去,连个包头都没有。一旦丢包,整个烧录就废了。

我常用的协议帧格式:

| 帧头 (2B) | 长度 (2B) | 命令 (1B) | 数据 (N B) | 校验 (2B) | 帧尾 (2B) |
| 0xAA 0x55 | 0x00 0x10 | 0x01      | ...        | CRC16     | 0x0D 0x0A |
  • 帧头/帧尾:用于同步,防止粘包。
  • 长度:整个帧的长度,方便接收方解析。
  • 命令:比如 0x01 表示开始烧录,0x02 表示校验。
  • 校验:CRC16 或 CRC32,别用累加和,太容易冲突了。

我曾经踩过的坑: 有一版协议没加帧尾,结果下位机收到半个包就开始解析,直接烧进去一堆乱码。从那以后,我强制要求:帧头帧尾必须成对出现,缺一不可。

4.3 数据库设计:每一颗芯片都得有据可查

产线烧录,不是烧完就完事了。出了问题要追溯,客户要报告,质量部门要分析。没有数据库,这些事根本干不了。

我个人习惯用 SQLite,轻量、免安装、单文件。对于单机产线,完全够用。如果是多工位联网,可以考虑 MySQL 或 PostgreSQL。

4.3.1 核心表结构

我设计了三张核心表:

1. 烧录任务表 (burn_task)

CREATE TABLE burn_task (
    task_id       INTEGER PRIMARY KEY AUTOINCREMENT,
    product_name  TEXT NOT NULL,        -- 产品型号
    firmware_ver  TEXT NOT NULL,        -- 固件版本
    firmware_hash TEXT NOT NULL,        -- 固件哈希,防篡改
    total_count   INTEGER DEFAULT 0,    -- 计划烧录数量
    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP
);

2. 烧录记录表 (burn_record)

CREATE TABLE burn_record (
    record_id     INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id       INTEGER NOT NULL,     -- 关联任务
    chip_uid      TEXT NOT NULL,        -- 芯片唯一ID
    result        INTEGER NOT NULL,     -- 0=失败, 1=成功
    error_code    INTEGER DEFAULT 0,    -- 错误码
    burn_time     INTEGER,              -- 烧录耗时(ms)
    operator      TEXT,                 -- 操作员
    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (task_id) REFERENCES burn_task(task_id)
);

3. 错误码表 (error_code)

CREATE TABLE error_code (
    code    INTEGER PRIMARY KEY,
    message TEXT NOT NULL               -- 错误描述
);

关键点: chip_uid 字段一定要有。每一颗芯片都有唯一 ID(比如 STM32 的 UID),烧录时读出来存好。以后客户说“这批芯片有问题”,你查一下就知道是哪颗、什么时候烧的、谁操作的。

4.3.2 数据库设计避坑指南

  • 别用自增 ID 做主键:对于烧录记录,建议用 chip_uid + task_id 做联合唯一索引,防止重复记录。
  • 定期归档:产线一天可能产生几万条记录。我建议每个月归档一次,把旧数据导出到 CSV 或备份库,保持主库轻量。
  • 加索引:在 created_at 和 chip_uid 上加索引,查询速度能快几十倍。别问我怎么知道的——有一次客户要查三个月前的记录,我等了五分钟才出结果,场面一度非常尴尬。

一个小技巧: 在烧录记录表里加一个 burn_time 字段,记录每次烧录的耗时。长期统计下来,你能发现哪些工位效率低,哪些芯片批次有问题。数据驱动优化,比拍脑袋靠谱多了。