Add CAN1 100 ms heartbeat with verified firmware and updated protocol PDF
This commit is contained in:
@@ -127,7 +127,7 @@ def export(font_path):
|
||||
canvas.line(40, 31, page_width-40, 31)
|
||||
canvas.setFont('AMIChinese', 8)
|
||||
canvas.setFillColor(colors.HexColor('#506775'))
|
||||
canvas.drawString(40, 19, 'AMI-2.4 | 2026-10-03 | CAN1 500K / CAN2 250K')
|
||||
canvas.drawString(40, 19, 'AMI-2.5 | 2026-10-04 | CAN1 500K / CAN2 250K')
|
||||
canvas.drawRightString(page_width-40, 19, f'{doc.page}')
|
||||
canvas.restoreState()
|
||||
|
||||
@@ -136,7 +136,7 @@ def export(font_path):
|
||||
leftMargin=40, rightMargin=40,
|
||||
topMargin=36, bottomMargin=44,
|
||||
title='AMI CAN 报文说明', author='AMI / YMR',
|
||||
subject='AMI CAN DBC v2.4: CAN1 500K / CAN2 250K')
|
||||
subject='AMI CAN DBC v2.5: CAN1 500K / CAN2 250K')
|
||||
document.build(story, onFirstPage=footer, onLaterPages=footer)
|
||||
print(OUTPUT)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Generate the Chinese handoff document directly from AMI.dbc."""
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import cantools
|
||||
@@ -20,9 +21,15 @@ def can2_dbc_text():
|
||||
]:
|
||||
assert old in text, old
|
||||
text = text.replace(old, new)
|
||||
return text
|
||||
text = re.sub(r'BO_ 769 AMI_Heartbeat:.*?(?=BO_)', '', text, flags=re.S)
|
||||
text = '\n'.join(line for line in text.splitlines()
|
||||
if not line.startswith(('CM_ BO_ 769 ', 'BA_ "GenMsgCycleTime" BO_ 769 ')))
|
||||
text = text.replace('AMI heartbeat is standard 0x301 every 100 ms on CAN1 only.',
|
||||
'AMI heartbeat 0x301 is CAN1 only and is not included in this CAN2 file.')
|
||||
return text.rstrip() + '\n'
|
||||
|
||||
PURPOSE = {
|
||||
0x301: 'AMI -> VCU(仅 CAN1):版本、序号、运行时间',
|
||||
0x183: 'VCU -> AMI:实际当前任务、锁定状态、EBS 状态',
|
||||
0x182: 'VCU / 测试器 -> AMI:扭矩千分、电机温度、MCU 温度',
|
||||
0x300: 'AMI -> VCU:用户确认后的任务选择请求',
|
||||
@@ -51,16 +58,16 @@ def render():
|
||||
'| --- | --- | --- | --- |',
|
||||
'| CAN1 | 500 kbit/s | PA11 / PA12 | [AMI.dbc](../AMI.dbc) |',
|
||||
'| CAN2 | 250 kbit/s | PB0 / PB1 | [AMI_CAN2_250k.dbc](../AMI_CAN2_250k.dbc) |', '',
|
||||
'DBC 的 `Baudrate` / `AMIPort` 分别标注所属总线。每份 DBC 均含 12 条报文、49 个信号;'
|
||||
'同一份报文表适用于两路。CAN 工具必须按接入端口设置速率,导入 DBC 不一定自动改硬件速率。'
|
||||
'DBC 的 `Baudrate` / `AMIPort` 分别标注所属总线。CAN1 DBC 含 13 条报文、53 个信号;CAN2 DBC 含 12 条报文、49 个信号;'
|
||||
'仅 CAN1 包含 0x301 心跳,其余报文布局相同。CAN 工具必须按接入端口设置速率,导入 DBC 不一定自动改硬件速率。'
|
||||
'实际接法:SDC 500 kbit/s 接 CAN1,BMS 250 kbit/s 接 CAN2。', '',
|
||||
'FDCAN 时钟 64 MHz,时钟分频为 1;NominalPrescaler 为 CAN1=8、CAN2=16,'
|
||||
'两路均使用 Sync=1、TimeSeg1=13、TimeSeg2=2、SJW=1。'
|
||||
'速率 = 64 MHz / Prescaler / (1+13+2);采样点 = (1+13)/16 = 87.5%。', '',
|
||||
'本 DBC 包含 AMI 唯一发送的 `0x300` 任务请求,以及 AMI 当前识别的 SDC / BMS 和自定义显示输入。'
|
||||
'本 DBC 包含 AMI 发送的 `0x300` 任务请求和 `0x301` 周期心跳,以及 AMI 当前识别的 SDC / BMS 和自定义显示输入。'
|
||||
'SDC `0x6A3` DAC 控制、`0x6A5` ASSI 控制、BMS 其他电芯/故障字段不属于此接口子集。'
|
||||
'`0x182` 新增扭矩千分、电机温度和电机控制器 MCU 温度的仪表显示输入;'
|
||||
'发送者负责转换实际电机控制器协议。`0x183` 为新增 VCU 状态反馈,需 VCU 按此自定义接口发送;未定义标定后的车速或 AMI 周期心跳。'
|
||||
'发送者负责转换实际电机控制器协议。`0x183` 为新增 VCU 状态反馈,需 VCU 按此自定义接口发送;未定义标定后的车速。'
|
||||
'旧 `0x180 / 0x181 / 0x280` 输入已停用。', '',
|
||||
'SDC 字段依据 `SDC_ACQ_DAC.dbc` VERSION 1.1,保持其信号名、位布局、缩放和单位;'
|
||||
'仅将输入接收节点设为 AMI。BMS 字段依据 '
|
||||
@@ -69,6 +76,7 @@ def render():
|
||||
'DBC 为 ASCII 编码,注释和枚举使用英文,便于导入 CAN 工具;本文为 UTF-8 中文。'
|
||||
'物理值 = 原始值 × Factor + Offset;`0x182` 的三个数值为有符号 int16,其他数值按表定义。'
|
||||
'下表范围为接口允许范围,不表示车辆正常工作范围。', '',
|
||||
'## AMI 周期心跳 0x301\n\n2026-10-04 新增:标准 ID `0x301`、Classic CAN 数据帧、DLC 8。仅 CAN1(500 kbit/s)每 **100 ms** 尝试发送,CAN2 不发送心跳,不依赖接收活动或屏幕连接。首帧在启动约 100 ms 后发送。\n\n| 字节 | 含义 |\n| --- | --- |\n| 0 | 协议版本,固定 1 |\n| 1 | 保留,固定 0 |\n| 2..3 | uint16 小端心跳序号,首帧 0,每个调度周期加 1,65535 后回到 0 |\n| 4..7 | uint32 小端 `HAL_GetTick()` 运行时间,单位 ms,自然回绕 |\n\n例如运行 100 ms 的首帧:`01 00 00 00 64 00 00 00`。任务请求先处理,心跳仅在发送 FIFO 至少有两个空位时入队,为任务保留一个空位;忙或入队失败时跳过本周期,不在软件中积压或补发。硬件自动重传仍启用,已入硬件 FIFO 的帧可能延迟到总线恢复后发出。100 ms 为主循环调度周期,实际总线时刻受仲裁/错误重传影响。序号按调度递增,因此丢帧时可以跳号。\n\n仅表示 AMI 主循环运行,不表示屏幕、VCU 或车辆状态正常。2026-10-04 已烧录并全量回读一致;CAN1 抓取 121 帧,间隔 99.952~100.180 ms,序号连续,详见 [心跳测试报告](AMI心跳测试报告.md)。CAN2 未接线抓帧。\n\n',
|
||||
'## 报文总览', '',
|
||||
'| CAN ID | 类型 | DLC | 报文名 | 方向和作用 | 周期 / 触发 |',
|
||||
'| --- | --- | --- | --- | --- | --- |',
|
||||
@@ -82,7 +90,7 @@ def render():
|
||||
'', '扩展帧在 DBC 的 `BO_` 十进制 ID 中设置 bit31:'
|
||||
'`0x186040F3 -> 2556444915`,`0x186140F3 -> 2556510451`。'
|
||||
'CAN 工具中使用实际 29 位仲裁 ID 并选择扩展帧;bit31 不是总线上发送的仲裁 ID。', '',
|
||||
'`GenMsgCycleTime` 填写已确认的 SDC 周期 100 / 500 / 20 ms,以及自定义 `0x182` 的约定周期 100 ms。'
|
||||
'`GenMsgCycleTime` 填写已确认的 SDC 周期 100 / 500 / 20 ms,以及 `0x182`、`0x183` 和 `0x301` 的约定周期 100 ms。'
|
||||
'其余帧未填写周期,默认 0 表示未指定,不据此推断 BMS 周期。', '',
|
||||
'## 信号布局', '',
|
||||
'Start 为 DBC 起始位,位号从 0 开始。Intel `@1` 从最低有效位起算;'
|
||||
@@ -220,8 +228,8 @@ def render():
|
||||
'# 若同时检出 SDC 仓库,可额外比较五条输入帧的原始布局:',
|
||||
'python scripts/verify_dbc.py --sdc-dbc ../SDC_ACQ_DAC/SDC_ACQ_DAC.dbc',
|
||||
'```', '',
|
||||
'校验覆盖严格 DBC 解析(长度、信号越界与重叠)、12 个 ID 与固件宏对应、'
|
||||
'标准/扩展格式、49 个信号、发送/接收节点、SDC / 自定义帧周期、已知报文解码、'
|
||||
'校验覆盖严格 DBC 解析(长度、信号越界与重叠)、13 个 ID 与固件宏对应、'
|
||||
'标准/扩展格式、53 个信号、发送/接收节点、SDC / 自定义帧周期、已知报文解码、'
|
||||
'六个任务号与计数边界编码、大端偏置电流、无效温度枚举、全负温度和截短 DLC。'
|
||||
'已安装并验证 cantools 44.1.0,接口参考 [cantools 官方文档](https://cantools.readthedocs.io/en/latest/)。', '',
|
||||
'上述是静态及软件编解码验证,另校验两份 DBC 的速率、相同信号布局及源码 / CubeMX 位时序一致。'
|
||||
|
||||
+15
-6
@@ -73,28 +73,37 @@ def main():
|
||||
header = (ROOT / 'firmware/Core/Inc/ami_protocol.h').read_text(encoding='utf-8')
|
||||
identifiers = {int(value, 16) for value in re.findall(
|
||||
r'^#define\s+AMI_CAN_ID_\w+\s+(0x[\dA-Fa-f]+)', header, re.MULTILINE)}
|
||||
expected = {0x182:(False,8), 0x183:(False,8), 0x300:(False,8), 0x6A0:(False,8), 0x6A1:(False,2),
|
||||
expected = {0x182:(False,8), 0x183:(False,8), 0x300:(False,8), 0x301:(False,8), 0x6A0:(False,8), 0x6A1:(False,2),
|
||||
0x6A2:(False,8), 0x6A4:(False,4), 0x6A6:(False,8),
|
||||
0x140:(False,8), 0x156:(False,8),
|
||||
0x186040F3:(True,8), 0x186140F3:(True,8)}
|
||||
actual = {m.frame_id:(m.is_extended_frame,m.length) for m in db.messages}
|
||||
assert actual == expected, actual
|
||||
assert set(actual) == identifiers, (actual, identifiers)
|
||||
assert len(db.messages) == 12
|
||||
assert sum(len(m.signals) for m in db.messages) == 49
|
||||
assert len(db.messages) == 13
|
||||
assert sum(len(m.signals) for m in db.messages) == 53
|
||||
for m in db.messages:
|
||||
if m.frame_id == 0x300:
|
||||
if m.frame_id in (0x300, 0x301):
|
||||
assert m.senders == ['AMI'] and m.receivers == {'VCU'}
|
||||
else:
|
||||
assert m.receivers == {'AMI'}
|
||||
assert m.senders == (['VCU'] if m.frame_id in (0x182,0x183) else
|
||||
['SDC_ACQ_DAC'] if 0x6A0 <= m.frame_id <= 0x6A6 else ['BMS'])
|
||||
assert db.dbc.attributes['Baudrate'].value == 500000
|
||||
for ident, cycle in [(0x182,100), (0x183,100), (0x6A0,100), (0x6A1,500), (0x6A2,20)]:
|
||||
for ident, cycle in [(0x301,100), (0x182,100), (0x183,100), (0x6A0,100), (0x6A1,500), (0x6A2,20)]:
|
||||
assert db.get_message_by_frame_id(ident).cycle_time == cycle
|
||||
for ident in [0x300,0x6A4,0x6A6,0x140,0x156,0x186040F3,0x186140F3]:
|
||||
assert db.get_message_by_frame_id(ident).cycle_time in (None,0)
|
||||
|
||||
assert len(db2.messages) == 12
|
||||
assert all(m.frame_id != 0x301 for m in db2.messages)
|
||||
heartbeat = db.get_message_by_frame_id(0x301)
|
||||
for sequence, uptime in ((0,100), (65535,4294967295), (0,0)):
|
||||
values = dict(Protocol_Version=1, Reserved=0,
|
||||
Heartbeat_Sequence=sequence, MCU_Uptime=uptime)
|
||||
raw = bytes([1,0]) + sequence.to_bytes(2,'little') + uptime.to_bytes(4,'little')
|
||||
assert heartbeat.encode(values) == raw
|
||||
assert heartbeat.decode(raw) == values
|
||||
checks = []
|
||||
def decode(ident, data):
|
||||
checks.append({'id':hex(ident),'data':data})
|
||||
@@ -239,7 +248,7 @@ def main():
|
||||
db2.decode_message(ident, payload, decode_choices=decode_choices)
|
||||
print(json.dumps({'result':'passed','messages':len(db.messages),'signals':sum(len(m.signals) for m in db.messages),
|
||||
'known_payload_checks':len(checks),'all_task_ids_and_counter_edges':True,
|
||||
'ports_bitrate':bitrates,'both_dbc_layouts_and_payloads_match':True,
|
||||
'ports_bitrate':bitrates,'shared_dbc_layouts_and_payloads_match':True, 'heartbeat_can1_only':True,
|
||||
'firmware_and_ioc_bit_timing_match':True,
|
||||
'firmware_ids_match':True,'sdc_source_layout_match':source_check,
|
||||
'display_sender_known_bytes_and_argument_rejection':True,
|
||||
|
||||
Reference in New Issue
Block a user