Files
AMI/scripts/export_dbc_docs.py
T

151 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Generate the Chinese handoff document directly from AMI.dbc."""
import argparse
from pathlib import Path
import cantools
ROOT = Path(__file__).resolve().parents[1]
OUTPUT = ROOT / 'docs/AMI_CAN_DBC说明.md'
PURPOSE = {
0x300: 'AMI -> VCU:用户确认后的任务选择请求',
0x6A0: 'SDC -> AMI:板标识、版本、序号、上电时间',
0x6A1: 'SDC -> AMI:低压电池电压,精确 DLC=2',
0x6A2: 'SDC -> AMI:PA0 / PA2 / PA6 / PB14 原始频率',
0x6A4: 'SDC -> AMI:DAC 设置应答,识别但不显示',
0x6A6: 'SDC -> AMI:PD0..PD14 原始电平和变化掩码',
0x140: 'BMS -> AMI:高压总电压、带偏置的总电流',
0x156: 'BMS -> AMI:八路实际温度,AMI 派生最高有效温度',
0x186040F3: 'BMS -> AMI:高压总电压、总电流扩展帧别名',
0x186140F3: 'BMS -> AMI:最高单体温度;不使用 BMS 低压字段',
}
def render():
db = cantools.database.load_file(ROOT / 'AMI.dbc', strict=True)
lines = [
'# AMI CAN DBC 说明', '',
f'版本:`{db.version}`。由 [AMI.dbc](../AMI.dbc) 自动生成,10 条报文、33 个信号。', '',
'## 使用范围', '',
'CAN1 / CAN2 功能相同,均为 500 kbit/s、Classic CAN、87.5% 采样点。'
'节点 AMI 为仪表 MCU,VCU 为任务请求的逻辑接收者,SDC_ACQ_DAC 和 BMS 为数据源。', '',
'本 DBC 包含 AMI 唯一发送的 `0x300` 任务请求,以及 AMI 当前识别的 SDC / BMS 输入。'
'SDC `0x6A3` DAC 控制、`0x6A5` ASSI 控制、BMS 其他电芯/故障字段不属于此接口子集。'
'没有定义电机温度、逆变器温度、扭矩、标定后的车速、AMI 周期心跳或 VCU 执行反馈。'
'旧 `0x180 / 0x181 / 0x280` 输入已停用。', '',
'SDC 字段依据 `SDC_ACQ_DAC.dbc` VERSION 1.1,保持其信号名、位布局、缩放和单位;'
'仅将输入接收节点设为 AMI。BMS 字段依据 '
'[BmsMonitor-CANTest](https://git.acetaffy.ltd/acetaffy/BmsMonitor-CANTest) '
'提交 `e65c95203d1689df05343775982d7e85bd782646` 的 `BmsProtocolParser.cs`。', '',
'DBC 为 ASCII 编码,注释和枚举使用英文,便于导入 CAN 工具;本文为 UTF-8 中文。'
'物理值 = 原始无符号值 × Factor + Offset;下表范围为编码范围,不表示车辆正常工作范围。', '',
'## 报文总览', '',
'| CAN ID | 类型 | DLC | 报文名 | 方向和作用 | 周期 / 触发 |',
'| --- | --- | --- | --- | --- | --- |',
]
triggers = {0x300:'确认任务触发',0x6A4:'DAC 设置后应答',0x6A6:'启动 / 电平变化'}
for m in db.messages:
cycle = f'{m.cycle_time} ms' if m.cycle_time else triggers.get(m.frame_id, '源协议未指定')
lines.append(f'| `0x{m.frame_id:X}` | {"扩展" if m.is_extended_frame else "标准"} | '
f'{m.length} | `{m.name}` | {PURPOSE[m.frame_id]} | {cycle} |')
lines += [
'', '扩展帧在 DBC 的 `BO_` 十进制 ID 中设置 bit31:'
'`0x186040F3 -> 2556444915`,`0x186140F3 -> 2556510451`。'
'CAN 工具中使用实际 29 位仲裁 ID 并选择扩展帧;bit31 不是总线上发送的仲裁 ID。', '',
'`GenMsgCycleTime` 仅填写已确认的 SDC 周期 100 / 500 / 20 ms。'
'其余帧未填写周期,默认 0 表示未指定,不据此推断 BMS 周期。', '',
'## 信号布局', '',
'Start 为 DBC 起始位,位号从 0 开始。Intel `@1` 从最低有效位起算;'
'Motorola `@0` 从最高有效位起算。例如 BMS 的 `7|16@0+` 对应字节 0..1 大端无符号数,'
'`23|16@0+` 对应字节 2..3。单字节温度的字节序不改变实际解码。', '',
]
for m in db.messages:
lines += [f'### 0x{m.frame_id:X} - {m.name}', '',
'| 信号 | Start | 位数 | 字节序 | Factor | Offset | 单位 | 编码范围 |',
'| --- | --- | --- | --- | --- | --- | --- | --- |']
for s in m.signals:
lines.append(f'| `{s.name}` | {s.start} | {s.length} | '
f'{"Intel" if s.byte_order=="little_endian" else "Motorola"} | '
f'{s.scale:g} | {s.offset:g} | {s.unit or "-"} | '
f'{s.minimum:g}..{s.maximum:g} |')
choices = [f'`{s.name}`:' + ','.join(f'{value} = {name}' for value,name in s.choices.items())
for s in m.signals if s.choices]
if choices:
lines += ['', ';'.join(choices) + '。']
lines += ['']
lines += [
'## AMI 任务请求语义', '',
'| Task_ID | 任务 |', '| --- | --- |',
'| 1 | 八字绕环 |', '| 2 | 直线加速 |', '| 3 | 高速循迹 |',
'| 4 | 车检测试 |', '| 5 | EBS 测试 |', '| 6 | 手动驾驶 |', '',
'用户确认时,屏幕通过串口发送 `AA 55 01 NN 0D 0A`,MCU 才产生 CAN `0x300` 请求;'
'取消不产生请求。Protocol_Version 和 Request_Source 固定为 1;Request_Sequence '
'从 MCU 启动后的 0 开始,在各目标口完成入队后加一,255 后回到 0。MCU_Uptime '
'为请求入队时的 `HAL_GetTick()` 毫秒值,小端 uint32,自然回绕。', '',
'优先发送到最近 500 ms 内收到过有效协议报文的 CAN 口;两路活跃则发送两路;'
'没有活跃口时尝试两路。若未连接端口自动重发导致 TX FIFO 填满,现有队列可能等待。'
'该策略沿用当前固件。任务表示操作员请求,VCU 负责裁决和安全互锁;'
'未定义应用层执行确认、运行完成、故障回传,也不把任务号换成 ASSI 灯状态。', '',
'## 显示缩放与失效规则', '',
'| 显示项 | 输入和显示规则 | 失效条件 |', '| --- | --- | --- |',
'| 低压电池 | `0x6A1` uint16 mV;`lvbat.val=(mV+5)/10`,显示两位小数 V | 1500 ms 无有效 ADC |',
'| 高压电压 | BMS 大端 uint16 × 0.1 V;现有控件四舍五入到整数 V | 2000 ms 无有效 BMS 状态 |',
'| 高压电流 | BMS 大端 uint16 × 0.1 - 500 A;四舍五入到整数 A | 2000 ms 无有效 BMS 状态 |',
'| 最高单体温度 | `0x156` 有效通道最大值,或扩展帧字节 4 - 40 | 全无效或 2000 ms 无有效温度源 |', '',
'电流字段是带偏置的 uint16,不能按 int16 解码。温度原始 `0xFF` 为无效;'
'DBC 枚举将其标为 Invalid。关闭枚举解码的工具可能显示 215,聚合时必须先排除 '
'原始 255,不能把它当作 215 ℃。有效实际温度范围为 -40..214 ℃。'
'第 9/10 路虚拟温度没有总线字段,最高温度也属于 MCU 派生结果,不新增虚构信号。', '',
'BMS 扩展帧的其他字段没有纳入本 DBC,尤其字节 6..7 的低压电压不覆盖 SDC ADC。'
'两类温度帧共用最高温度显示,后收到的温度帧更新该显示及有效性。'
'SDC GPIO 原始电平尚无具名安全信号映射和正常极性,因此 debug 保持未知;'
'轮频 Hz 尚无周长和每圈脉冲数,不能直接当 km/h。', '',
'有效值显示白色;未知、无效、超时显示深灰色,历史数值保留但不作为有效测量。'
'各源独立超时,其他流量不续期。每 5 秒重新同步界面,刚连接需等待首轮同步。', '',
'## 报文样例', '',
'| ID / DLC | 数据(十六进制) | 解码及仪表显示 |', '| --- | --- | --- |',
'| `0x300 / 8` | `01 02 00 01 E8 03 00 00` | 直线加速请求、序号 0、来源 AMI、上电 1000 ms |',
'| `0x6A1 / 2` | `0C 30` | ADC 12300 mV;低压 12.30 V |',
'| `0x6A2 / 8` | `32 00 64 00 96 00 C8 00` | PA0 / PA2 / PA6 / PB14:50 / 100 / 150 / 200 Hz |',
'| `0x140 / 8` | `0F A0 14 03 00 00 00 05` | 400.0 V / +12.3 A;显示 400 / 12 |',
'| `0x186040F3 / 8` 扩展 | `10 73 10 D1 00 00 00 05` | 421.1 V / -69.5 A;显示 421 / -70 |',
'| `0x156 / 8` | `42 FF 43 47 37 0A 50 41` | 第二路无效,有效最高温度 40 ℃ |',
'| `0x186140F3 / 8` 扩展 | `10 04 0C E4 3F 17 00 7B` | 最高温度 23 ℃;BMS 低压字段不使用 |', '',
'## 验证和复现', '',
'在仓库根目录运行:', '', '```powershell',
'python -m pip install -r scripts/requirements.txt',
'python scripts/verify_dbc.py',
'python scripts/export_dbc_docs.py --check',
'# 若同时检出 SDC 仓库,可额外比较五条输入帧的原始布局:',
'python scripts/verify_dbc.py --sdc-dbc ../SDC_ACQ_DAC/SDC_ACQ_DAC.dbc',
'```', '',
'校验覆盖严格 DBC 解析(长度、信号越界与重叠)、10 个 ID 与固件宏对应、'
'标准/扩展格式、33 个信号、发送/接收节点、SDC 周期、24 个已知报文解码、'
'六个任务号与计数边界编码、大端偏置电流、无效温度枚举、全负温度和截短 DLC。'
'已安装并验证 cantools 44.1.0,接口参考 [cantools 官方文档](https://cantools.readthedocs.io/en/latest/)。', '',
'上述是静态及软件编解码验证。之前 CAN 盒 -> 实物 AMI CAN1 / MCU -> USART2 -> COM4 '
'-> 官方 HMI 调试器的实际更新和六个任务回传见 [联调报告](SDC_BMS联调报告.md)。'
'物理串口屏、CAN2 接线、真实 SDC/BMS 节点和车辆执行动作尚未实测;'
'文档生成和 DBC 解析不替代这些测试。', '',
'修改 AMI.dbc 后运行 `python scripts/export_dbc_docs.py` 更新本文;'
'固件字节布局、DBC 与此文档需同时保持一致。', '',
]
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
text = render()
if args.check:
assert OUTPUT.read_text(encoding='utf-8') == text, 'DBC document is stale; run export_dbc_docs.py'
print('DBC documentation matches AMI.dbc')
else:
OUTPUT.write_text(text, encoding='utf-8', newline='\n')
print('Generated docs/AMI_CAN_DBC说明.md')
if __name__ == '__main__':
main()