1. 项目概述:CANdb++不是“数据库软件”,而是CAN协议工程的神经中枢
很多人第一次看到“CANdb++数据库操作”这个标题,第一反应是——这不就是个类似MySQL或Oracle的数据库管理工具?甚至在搜索引擎里输入关键词,会混入大量“数据库增删改查”“数据库同步工具”“达梦数据库”“人大金仓数据库docker”这类通用数据库术语,导致信息严重失焦。但我要先说清楚:CANdb++根本不是传统意义上的关系型数据库(RDBMS),它不存业务数据、不跑SQL语句、也不提供JDBC连接池。它是一个专为汽车电子与嵌入式系统设计的CAN通信协议描述文件编辑与管理工具,核心对象是.dbc(Database CAN)文件——一种纯文本格式的协议定义规范,本质是“通信协议的元数据说明书”。
我做车载ECU开发近八年,从BCM到ADAS域控制器,每天打交道最多的就是.dbc文件。CANdb++就是我们工程师手边的“CAN协议字典+信号翻译器+报文校验仪”。它不处理实时CAN总线数据流,但所有CAN分析仪(如Vector CANoe、Peak PCAN)、AUTOSAR配置工具(如EB tresos)、ECU刷写工具(如ETAS INCA)都依赖它导出的.dbc文件来解析原始十六进制报文。比如你用CANoe抓到一帧0x123报文,里面0x45 0x67 0x89 0x01四个字节,没有.dbc文件,你永远不知道这是车速信号(0~255 km/h)、还是油门开度(0~100%)、或是某个诊断DTC码。而CANdb++就是让你把这串数字翻译成人类可读语义的唯一桥梁。
它的“数据库操作”实质是对CAN协议结构化定义的增删改查:添加一个新信号(Signal),设置其起始位、长度、字节序、缩放因子(Scale)、偏移量(Offset)、物理单位(Unit);修改某条报文(Message)的ID、周期、发送节点;删除已废弃的ECU节点(Node);或者批量导入/导出信号列表。这些操作不涉及事务、锁机制或索引优化,但每一步都直接影响整车网络通信的正确性——一个信号长度填错2位,实车测试时仪表盘车速可能跳变;一个缩放因子设反,电池管理系统BMS上报的SOC值会直接翻倍。所以,“CANdb++数据库操作”的真实价值,从来不是技术炫技,而是保障汽车电子系统通信零歧义的工程基线。适合谁?不是DBA,而是汽车电子工程师、功能安全工程师、HIL测试工程师、AUTOSAR开发者——只要你的工作需要和CAN总线打交道,你就绕不开它。
2. 核心设计逻辑:为什么CANdb++用.dbc而非SQL?协议描述的本质决定工具形态
2.1 协议描述文件 vs 业务数据存储:两类完全不同的数据范式
理解CANdb++的第一道门槛,是彻底分清“协议描述”和“业务数据”这两个概念。我们常接触的MySQL、Oracle、达梦数据库,解决的是“数据怎么存、怎么查、怎么保证一致性”的问题:用户订单表有id、user_id、amount、status字段,要支持高并发写入、复杂关联查询、ACID事务。而.dbc文件解决的是“通信双方如何约定同一串二进制数据的含义”问题:CAN总线上一帧报文固定8字节,发送方按规则打包,接收方按同一规则解包。它不关心这条报文被发了多少次、历史记录存多久,只关心“此刻这8个字节里,第3-5位是转向灯状态,0=关,1=左转,2=右转,3=双闪”。
提示:你可以把.dbc文件想象成一份“CAN总线上的摩斯电码对照表”。摩斯电码里“·−”代表A,“−−−”代表O,但电码本身不存储“今天张三买了三瓶水”这件事。.dbc同理——它定义“0x100报文第0字节bit0=1表示左转向灯亮”,但不记录“2024-06-15 14:23:01左转向灯被触发了17次”。
这种本质差异直接决定了工具设计逻辑:
- 无服务进程:CANdb++是单机桌面应用(Windows平台),不启动后台服务,不监听端口,不维护连接池。打开.dbc文件即加载内存,关闭即释放——因为协议定义是静态快照,无需持续服务。
- 无查询语言:没有SELECT/INSERT/UPDATE语法。所有操作通过图形界面点击完成,底层直接修改文本文件。你“添加信号”本质是在.dbc文件里追加一段符合规范的文本行,如
SG_ BrakePressure : 8|12@1+ (0.1,0) [0|4095] "bar" Vector__XXX。 - 强格式约束:.dbc有严格语法(ISO 16750-3标准),字段顺序、分隔符(空格/冒号/竖线)、单位引号都不能错。CANdb++内置校验器,输入非法值(如信号长度设为13位)会立刻标红提示——这不是UI交互反馈,而是防止生成无效.dbc文件的硬性保护。
2.2 工程协同场景下的“数据库”隐喻:版本、引用、复用才是关键
虽然不叫数据库,但CANdb++在实际工程中承担着类似数据库的协同管理职能。一辆整车CAN网络可能有上百个ECU节点(发动机ECU、空调ECU、座椅ECU等),每个节点发送/接收数十条报文,每条报文含多个信号。所有这些定义必须全局一致,否则会出现“仪表盘显示油量50%,但中控屏显示30%”的荒谬场景。因此,CANdb++的“操作”本质是维护一个跨团队、跨车型、跨项目的协议定义中心。
典型协同流程如下:
- 基线定义:项目启动时,网络工程师用CANdb++创建初始.dbc文件,定义所有公共报文(如车速、转速、档位);
- 模块接入:各ECU供应商基于此基线文件,用CANdb++添加自己专属的报文和信号(如雷达ECU添加
ACC_TargetDistance信号); - 集成验证:系统集成工程师将所有供应商提交的.dbc片段合并,用CANdb++的“Compare”功能检查冲突(如两个ECU都定义了ID=0x200的报文,但信号布局不同);
- 发布归档:冻结版本后,导出.dbc文件给测试团队、产线刷写设备、售后诊断仪使用。
这个过程里,“数据库操作”对应的是:
- 版本控制类操作:不是Git commit,而是CANdb++的“Export DBC as…”导出带时间戳的文件(如
CAN_Matrix_V2.3_20240615.dbc),配合SVN/Perforce管理; - 引用完整性操作:当删除一个ECU节点时,CANdb++会自动检查该节点是否作为发送方出现在某条报文中,若存在则阻止删除并提示“节点XXX被报文YYY引用”;
- 批量复用操作:用“Import from Excel”功能,将Excel里整理好的信号列表(含Name、StartBit、Length、ByteOrder等列)一键导入.dbc,避免手动逐条添加——这比写SQL INSERT语句快十倍,且零语法错误风险。
2.3 为何不选通用数据库?性能、安全与标准兼容性的刚性约束
有人会问:既然要管理结构化协议数据,为什么不用SQLite或PostgreSQL?把信号存成表,用SQL查多方便?这问题我当年也想过,直到在一次量产项目中踩坑才彻底明白。当时团队尝试用Python脚本读取.dbc生成SQLite库,结果发现三个致命问题:
- 解析性能灾难:一个中等复杂度的整车.dbc文件(约5000行)用正则解析需200ms,而CANdb++原生C++解析仅8ms。HIL台架测试要求毫秒级响应,每次报文解析延迟叠加会导致仿真失真;
- 标准兼容断层:AUTOSAR工具链(如Vector DaVinci)只认标准.dbc格式。你用SQLite存的数据,无法直接导入DaVinci生成BSW代码,必须额外开发.dbc导出模块,且极易因字段映射错误导致生成代码编译失败;
- 权限与审计缺失:数据库的“用户权限”在CAN工程中毫无意义。工程师要么有权限改协议(需ECU负责人审批),要么没权限。而.dbc文件天然支持Windows文件级ACL,配合SVN的commit hook可轻松实现“只有网络组成员才能提交.dbc变更”。
所以CANdb++的设计哲学很务实:不做通用,只做极致。它把全部精力投入.dbc格式的深度解析、可视化编辑、标准兼容上,而不是分散在数据库引擎开发上。这就像螺丝刀不会去竞争扳手的功能——工具的价值在于精准匹配场景,而非功能堆砌。
3. 核心操作详解:从新建.dbc到导出Excel,每一步背后的工程意图
3.1 新建与导入:建立协议基线的两种路径
新建.dbc文件是所有工作的起点。在CANdb++中点击“File → New”,会弹出向导窗口,关键选项有:
- Database Name:建议用项目代号+网络名,如
GAC_A_Series_CANFD(广汽A系列CAN FD网络),避免用NewDatabase这类模糊名称; - Protocol:选择CAN或CAN FD。注意CAN FD支持64字节报文,若选错会导致后续添加信号时长度超限报错;
- Default Byte Order:Motorola(大端)或Intel(小端)。汽车电子行业90%用Motorola,因CAN协议本身按字节高位在前定义,选错会导致信号解析完全错误(如车速值显示为0xFFFF)。
注意:向导完成后,你会看到一个空的“Network Nodes”列表。此时不要急着添加信号,先右键“Network Nodes” → “Add Node”,创建至少两个节点(如
ECU_Gateway和ECU_Instrument)。因为.dbc标准要求每条报文必须有明确的发送方(Transmitter)和接收方(Receiver),空节点会导致后续报文无法关联。
导入现有.dbc是更常见场景。点击“File → Import → DBC File”,选择文件后,CANdb++会执行三步校验:
- 语法校验:检查是否符合ISO 16750-3语法(如
BO_开头定义报文,SG_开头定义信号); - ID冲突检测:扫描所有
BO_行的报文ID,若发现重复ID(如两条报文都是BO_ 0x100),会弹窗警告并列出冲突行号; - 信号重叠检测:对同一报文内所有信号,计算其位域范围(StartBit + Length),若出现重叠(如信号A占0-7位,信号B占4-11位),标红提示“Signal overlap in message 0x100”。
实操心得:我曾遇到供应商提交的.dbc文件因编码问题(UTF-8 with BOM)导致中文注释乱码,CANdb++无法识别。解决方案是用Notepad++打开文件,选择“编码 → 转为ANSI”,再保存导入——这是现场高频问题,务必记牢。
3.2 报文(Message)操作:定义通信骨架的四大核心参数
报文是.dbc的骨架,每条报文由BO_行定义,格式为BO_ ID Name: DLC Sender。其中DLC(Data Length Code)是关键,它决定报文实际数据字节数(CAN为0-8,CAN FD为0-64)。操作报文时,重点管控四个参数:
ID设置:
- 标准帧ID范围0x000-0x7FF(11位),扩展帧ID范围0x00000000-0xFFFFFFFF(29位)。汽车电子普遍用标准帧,ID分配遵循“功能域+优先级”原则(如0x100-0x1FF为动力域,0x200-0x2FF为车身域);
- 实操禁忌:ID不能以0结尾(如0x100合法,0x1000非法),因CAN控制器硬件会截断高位,导致ID误判。
DLC配置:
- 常见误区是认为DLC=8就填8,但实际需根据信号总位宽向上取整字节。例如信号A占3位、信号B占5位、信号C占12位,总位宽20位→需3字节(24位),DLC应设3而非8;
- CAN FD下DLC可设为12/16/20/24/32/48/64,需与ECU固件支持的DLC列表匹配,否则报文被丢弃。
Sender节点绑定:
- 右键报文 → “Properties” → “Transmitter”,从下拉列表选择发送ECU。若列表为空,说明未创建对应Node——这是新手最常卡住的点;
- 一个报文只能有一个Sender,但可有多个Receiver(在
CM_ "Receiver"注释行中声明)。
周期(Cycle Time)设置:
- 在“Properties” → “Comment”中添加
CM_ "GenMsgCycleTime: 100"(单位ms),用于HIL仿真时自动生成报文; - 注意:此字段非.dbc标准字段,是Vector扩展,部分工具(如PCAN-View)不识别,需确认下游工具兼容性。
- 在“Properties” → “Comment”中添加
3.3 信号(Signal)操作:让二进制变成物理世界的钥匙
信号是.dbc的灵魂,定义如何从字节流中提取物理量。添加信号的入口是右键报文 → “Add Signal”。关键参数解析如下:
| 参数 | 含义 | 典型值 | 工程要点 |
|---|---|---|---|
| Start Bit | 信号在报文中的起始位(0为最低位) | 8(第2字节第0位) | Motorola下按字节高位在前,Intel下按字节低位在前;务必与ECU固件定义一致 |
| Length | 信号长度(bit) | 12(支持0-64) | 长度超限会截断,如12位信号存入8位变量导致溢出 |
| Byte Order | 字节序 | Motorola | 汽车电子默认,选错导致数值翻转(如100km/h显示为-100) |
| Value Type | 数据类型 | Signed(有符号)或Unsigned(无符号) | 温度常用Signed,车速常用Unsigned |
| Factor/Offset | 缩放因子与偏移量 | Factor=0.1, Offset=0 | 物理值 = (RawValue × Factor) + Offset。车速0.1表示Raw=100对应10km/h |
| Min/Max | 物理值范围 | Min=0, Max=255 | 用于CANoe仿真时边界值校验,超出范围报错 |
实操案例:添加“发动机转速”信号
- Start Bit:
16(第3字节起始) - Length:
16(0-65535 rpm,需16位) - Byte Order:
Motorola - Value Type:
Unsigned - Factor:
0.25(Raw=100 → 25rpm) - Offset:
0 - Unit:
"rpm" - Min/Max:
0/16383(对应Raw 0-65535)
提示:Factor计算有技巧。若ECU固件用16位无符号整数存转速,满量程65535对应8000rpm,则Factor = 8000 / 65535 ≈ 0.12207。但为避免浮点误差,工程师常取整为0.122或0.125(1/8),此时需同步调整Max值确保覆盖全量程。
3.4 批量操作:用Excel和模板提升百倍效率
手工添加信号是体力活,大型项目动辄上千信号。CANdb++提供两大批量利器:
Excel导入:
- 准备Excel文件,表头必须包含:
Name(信号名)、StartBit、Length、ByteOrder、ValueType、Factor、Offset、Min、Max、Unit; - 点击“File → Import → Excel File”,选择文件;
- 映射列:将Excel列拖拽到对应.dbc字段(如Excel的“A列”拖到“Name”框);
- 关键设置:“Apply to Message”选择目标报文,“Default Transmitter”指定发送节点。
常见问题:Excel日期列被自动转成数字(如2024/6/15→45122)。解决方案:在Excel中选中该列 → 右键“设置单元格格式” → “文本”,再重新导入。
模板复用:
CANdb++支持“Template Database”功能。将常用信号(如车速、转速、档位)保存为模板.dbc,后续项目直接“File → Import → Template”,选择模板文件即可插入全套定义。我维护的模板库包含:
Powertrain_Template.dbc(动力域基础信号)Body_Template.dbc(车身域开关信号)Diag_Template.dbc(UDS诊断服务信号)
复用率超70%,新项目协议定义时间从2周缩短至3天。
3.5 导出与验证:确保.dbc能被下游工具正确识别
导出是操作闭环。点击“File → Export”,关键选项:
- Export Format:选“DBC File”(标准格式)或“ARXML”(AUTOSAR格式)。若对接Vector工具链,必须选DBC;若对接ETAS,可选ARXML;
- Encoding:选“ANSI”(Windows默认),避免UTF-8导致中文乱码;
- Include Comments:勾选此项,导出的.dbc会保留所有注释(如
CM_ "Engine torque signal"),便于团队阅读。
验证导出文件有效性:
- 用文本编辑器打开.dbc,搜索
BO_确认报文数量; - 用CANoe打开.dbc,观察“Network Explorer”中是否正常显示所有节点、报文、信号;
- 关键测试:在CANoe中新建一个“Simulation Block”,添加信号(如
EngineSpeed),设置值为1000,观察生成的报文数据字节是否与.dbc定义一致(如Factor=0.25时,Raw=4000 → 字节应为0F A0)。
注意:导出后务必用
fc命令行工具比对新旧文件差异(Windows下fc old.dbc new.dbc > diff.txt),确认仅修改了预期内容。曾有同事误操作导致信号位移,比对发现SG_ Speed : 0|16@1+被改成SG_ Speed : 1|16@1+,虽只差1位,但整车测试时车速显示全乱。
4. 高频问题排查:从“未打开com.stromplatform.wave.helper”到信号解析错误
4.1 启动报错:“未打开‘com.stromplatform.wave.helper’,因其包含恶意软件”
这是Windows Defender的误报,源于CANdb++调用的第三方组件(WaveHelper.dll)签名过期。解决方案分三步:
- 临时放行:右键系统托盘Windows Defender图标 → “打开Windows安全中心” → “病毒和威胁防护” → “管理设置” → 关闭“实时保护”(仅临时,操作完立即开启);
- 添加排除项:在同一页面 → “添加或删除排除项” → “添加排除项” → 选择CANdb++安装目录(如
C:\Program Files\CANdb++); - 终极方案:从Vector官网下载最新版CANdb++(v4.2+),新版已更新签名证书,彻底规避此问题。
提示:切勿从非官方渠道下载所谓“破解版”,我见过三次因盗版软件植入挖矿木马,导致HIL台架CPU满载停机。
4.2 信号不显示/解析错误:八成源于字节序与起始位陷阱
现象:CANoe中导入.dbc后,信号值始终为0或乱码。排查路径:
- 确认字节序:在CANdb++中右键信号 → “Properties”,检查“Byte Order”是否为
Motorola。若ECU固件用Intel序,此处必须同步修改; - 验证起始位:用CANoe的“Trace”窗口抓一帧报文,记录原始字节(如
00 00 00 00 00 00 00 00),然后在.dbc中找到对应信号,手动计算其位域。例如信号StartBit=8, Length=12,应占据第2字节全部(bit0-7)和第3字节前4位(bit0-3); - 检查Factor/Offset:在CANoe中右键信号 → “Properties” → “Scaling”,确认缩放参数与.dbc完全一致。曾有项目因复制粘贴时小数点丢失(
0.1变成01),导致车速显示为10倍。
4.3 Excel导入失败:字符编码与列映射的隐形杀手
现象:导入后信号名变成乱码(如车速),或数值全为0。原因及解法:
- 乱码问题:Excel文件保存为UTF-8编码,但CANdb++只识别ANSI。解决:Excel另存为 → 编码选“Windows ANSI(CP1252)”;
- 数值为0:Excel中数字被识别为文本(单元格左下角绿色三角标)。解决:选中列 → 数据选项卡 → “文本转列” → 下一步 → 完成;
- 列映射错位:导入向导中未正确拖拽列。解决:取消勾选“Auto Map”,手动将Excel的“A列”拖到“Name”框,“B列”拖到“StartBit”框,逐一确认。
4.4 报文ID冲突:跨供应商集成时的定时炸弹
现象:合并多个.dbc文件后,CANoe报错“Duplicate message ID 0x200”。排查步骤:
- 在CANdb++中点击“Tools → Compare Databases”,选择两个.dbc文件;
- 对比结果中定位ID=0x200的报文,查看“Sender”字段:若A文件中Sender=
ECU_Radar,B文件中Sender=ECU_Camera,说明是不同ECU定义了同ID报文; - 解决方案:协商重分配ID(如雷达用0x201,摄像头用0x202),或采用扩展帧(0x10000000)避开冲突。
实操心得:我们建立《ID分配表》Excel,由网络组统一维护,所有供应商提交.dbc前必须查表确认ID唯一性。此表已迭代12版,累计规避37次ID冲突。
4.5 中文注释失效:非标准字段的兼容性雷区
现象:在.dbc中添加中文注释CM_ "发动机转速信号",但CANoe中不显示。原因:CANoe默认只解析标准注释字段(如CM_ SG_ "..."),对CM_ "..."全局注释支持有限。解决方案:
- 用标准信号注释:
CM_ SG_ EngineSpeed "发动机转速信号"; - 或在CANoe中启用“Display all comments”选项(Options → Preferences → DBC → Show all comments)。
5. 进阶实践:从协议管理到自动化集成的工作流升级
5.1 与Python脚本联动:用pandas自动化信号校验
CANdb++本身无批量校验功能,但可结合Python提升效率。我常用脚本检查信号Factor合理性:
import pandas as pd from dbc_parser import parse_dbc # 使用python-can的dbc模块 # 解析.dbc获取信号列表 signals = parse_dbc("GAC_A_Series.dbc") df = pd.DataFrame(signals) # 筛选车速相关信号 speed_signals = df[df['Name'].str.contains('Speed|speed')] print("车速信号Factor检查:") for _, sig in speed_signals.iterrows(): if sig['Factor'] < 0.05 or sig['Factor'] > 1.0: print(f"警告:{sig['Name']} Factor={sig['Factor']} 超出合理范围(0.05-1.0)")此脚本可集成到CI流程,在每次.dbc提交前自动运行,将人工检查从2小时缩短至5分钟。
5.2 与Git协同:用pre-commit钩子防低级错误
.dbc是文本文件,天然适配Git。但需防范语法错误提交。在.git/hooks/pre-commit中添加:
#!/bin/sh # 检查新增/修改的.dbc文件语法 for file in $(git diff --cached --name-only | grep "\.dbc$"); do if ! python -c "import can; can.dbc.load_file('$file')" 2>/dev/null; then echo "错误:$file 语法错误,请用CANdb++校验后提交" exit 1 fi done此钩子拦截90%的语法错误(如漏写BO_、信号位超限),避免污染主干分支。
5.3 与AUTOSAR工具链集成:从.dbc到BSW代码的无缝生成
在Vector DaVinci中,导入.dbc后可自动生成CAN通信栈代码。关键配置点:
- Network Configuration:在“CAN Network”视图中,右键网络 → “Import DBC”,选择.dbc文件;
- Signal Mapping:DaVinci自动映射信号到PDU,但需手动确认“Byte Order”和“Endianness”设置与.dbc一致;
- Code Generation:生成代码前,检查“Generate CAN Driver”选项是否启用,否则只生成配置文件不生成驱动代码。
曾有项目因忘记启用此选项,导致刷写后ECU无法收发CAN报文,返工耗时3天。教训:每次生成前必核对“Code Generation Settings”页签。
5.4 与HIL测试平台联动:用.dbc驱动仿真模型
在dSPACE SCALEXIO中,.dbc文件是仿真模型的输入源。操作流程:
- 将.dbc导入SCALEXIO ConfigurationDesk;
- 在“Model Interface”中,右键信号 → “Create Signal Interface”,自动生成Simulink模型输入端口;
- 在测试用例中,直接设置信号值(如
EngineSpeed = 2000),模型自动计算对应报文字节并发送至ECU。
优势:测试人员无需懂CAN协议细节,只需关注物理量(如“设置车速100km/h”),大幅提升测试效率。我们用此方法将HIL测试用例编写时间减少60%。
6. 经验总结:十年踩坑凝练的六条铁律
我在广汽、博世、德赛西威做过12个量产车型的CAN协议开发,从CAN 2.0B到CAN FD,从燃油车到智能电动车,这些经验不是来自手册,而是从一次次实车故障中抠出来的:
“DBC即法律”铁律:任何ECU固件、测试脚本、诊断仪,都必须100%遵循.dbc定义。曾有供应商擅自修改信号长度,理由是“节省1位空间”,结果导致仪表盘车速跳变。最终整车召回,代价远超1位存储成本。记住:.dbc不是建议,是通信契约。
“先测后改”铁律:修改.dbc后,必须用CANoe或PCAN-View抓取真实报文验证。我见过太多人凭经验修改Factor,结果实车测试时发现ECU固件实际用的是不同缩放算法,导致物理值偏差30%。真实报文永远比理论计算可靠。
“中文注释必加”铁律:所有信号、报文、节点必须添加中文注释(
CM_ SG_ xxx "左前门锁状态")。三年后你可能不记得LockStatus_FL是什么,但“左前门锁状态”一目了然。注释不是负担,是给未来自己留的救命稻草。“ID分配权唯一”铁律:整车CAN网络ID分配必须由单一权威(通常是网络架构师)掌控,禁止各ECU供应商自行分配。我们曾用共享Excel表管理ID,结果出现两次冲突,后来改用Confluence在线表格+编辑锁定,彻底解决。
“DBC版本号即车型号”铁律:.dbc文件名必须包含车型代号和版本号(如
GAC_A_Series_CANFD_V3.2_20240615.dbc),禁止用final_v2.dbc这类模糊命名。版本号变更必须同步更新所有下游工具链配置,否则HIL台架可能用旧.dbc跑新ECU,导致测试失效。“备份三份”铁律:.dbc文件必须本地、SVN服务器、离线硬盘各存一份。去年暴雨导致机房断电,SVN服务器损坏,幸亏有离线备份,否则整车网络协议重定义需2个月,项目直接延期。工具可以重装,协议定义丢了就是灾难。
最后分享一个小技巧:CANdb++的“Quick Search”(Ctrl+F)支持正则表达式。输入SG_.*Speed可瞬间定位所有含Speed的信号,比手动滚动查找快十倍。这个功能藏得深,但用熟了,一天能省下半小时——工程师的时间,就该花在解决真问题上,而不是找信号。