Set CAN1 to 500K and CAN2 to 250K with matching DBCs and PDF
This commit is contained in:
@@ -28,7 +28,7 @@ def inline(text):
|
||||
def link(match):
|
||||
label, target = match.groups()
|
||||
if not target.startswith(('http://', 'https://')):
|
||||
target = REMOTE + ('AMI.dbc' if target == '../AMI.dbc' else 'docs/' + target)
|
||||
target = REMOTE + (target[3:] if target.startswith('../') else 'docs/' + target)
|
||||
return f'<link href="{target}" color="#155E75">{label}</link>'
|
||||
|
||||
return re.sub(r'\[([^\]]+)\]\(([^)]+)\)', link, text)
|
||||
@@ -78,6 +78,7 @@ def export(font_path):
|
||||
proportions = {
|
||||
2: [0.15, 0.85],
|
||||
3: [0.17, 0.40, 0.43],
|
||||
4: [0.12, 0.15, 0.20, 0.53],
|
||||
6: [0.11, 0.055, 0.04, 0.28, 0.365, 0.15],
|
||||
8: [0.32, 0.055, 0.055, 0.10, 0.09, 0.08, 0.06, 0.24],
|
||||
}[count]
|
||||
@@ -126,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.0 | 2026-10-03 | CAN 接口说明')
|
||||
canvas.drawString(40, 19, 'AMI-2.1 | 2026-10-03 | CAN1 500K / CAN2 250K')
|
||||
canvas.drawRightString(page_width-40, 19, f'{doc.page}')
|
||||
canvas.restoreState()
|
||||
|
||||
@@ -135,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.0: SDC, BMS and task requests')
|
||||
subject='AMI CAN DBC v2.1: CAN1 500K / CAN2 250K')
|
||||
document.build(story, onFirstPage=footer, onLaterPages=footer)
|
||||
print(OUTPUT)
|
||||
|
||||
|
||||
@@ -6,6 +6,21 @@ import cantools
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUTPUT = ROOT / 'docs/AMI_CAN_DBC说明.md'
|
||||
CAN2_OUTPUT = ROOT / 'AMI_CAN2_250k.dbc'
|
||||
|
||||
|
||||
def can2_dbc_text():
|
||||
text = (ROOT / 'AMI.dbc').read_text(encoding='ascii')
|
||||
for old, new in [
|
||||
('BA_DEF_DEF_ "Baudrate" 500000;', 'BA_DEF_DEF_ "Baudrate" 250000;'),
|
||||
('BA_ "Baudrate" 500000;', 'BA_ "Baudrate" 250000;'),
|
||||
('"AMIPort" "CAN1";', '"AMIPort" "CAN2";'),
|
||||
('This file is CAN1; use AMI_CAN2_250k.dbc for CAN2.',
|
||||
'This file is CAN2; use AMI.dbc for CAN1.'),
|
||||
]:
|
||||
assert old in text, old
|
||||
text = text.replace(old, new)
|
||||
return text
|
||||
|
||||
PURPOSE = {
|
||||
0x300: 'AMI -> VCU:用户确认后的任务选择请求',
|
||||
@@ -27,8 +42,19 @@ def render():
|
||||
'# AMI CAN DBC 说明', '',
|
||||
f'版本:`{db.version}`。由 [AMI.dbc](../AMI.dbc) 自动生成,10 条报文、33 个信号。', '',
|
||||
'## 使用范围', '',
|
||||
'CAN1 / CAN2 功能相同,均为 500 kbit/s、Classic CAN、87.5% 采样点。'
|
||||
'CAN1 为 500 kbit/s,CAN2 为 250 kbit/s,均为 Classic CAN、87.5% 采样点。'
|
||||
'两路保留相同的接收 ID、解码与任务发送功能,MCU 不在两路之间转发原始报文。'
|
||||
'节点 AMI 为仪表 MCU,VCU 为任务请求的逻辑接收者,SDC_ACQ_DAC 和 BMS 为数据源。', '',
|
||||
'| 端口 | 速率 | RX / TX 引脚 | DBC 文件 |',
|
||||
'| --- | --- | --- | --- |',
|
||||
'| 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 均含 10 条报文、33 个信号;'
|
||||
'同一份报文表适用于两路。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 输入。'
|
||||
'SDC `0x6A3` DAC 控制、`0x6A5` ASSI 控制、BMS 其他电芯/故障字段不属于此接口子集。'
|
||||
'没有定义电机温度、逆变器温度、扭矩、标定后的车速、AMI 周期心跳或 VCU 执行反馈。'
|
||||
@@ -123,11 +149,13 @@ def render():
|
||||
'标准/扩展格式、33 个信号、发送/接收节点、SDC 周期、24 个已知报文解码、'
|
||||
'六个任务号与计数边界编码、大端偏置电流、无效温度枚举、全负温度和截短 DLC。'
|
||||
'已安装并验证 cantools 44.1.0,接口参考 [cantools 官方文档](https://cantools.readthedocs.io/en/latest/)。', '',
|
||||
'上述是静态及软件编解码验证。之前 CAN 盒 -> 实物 AMI CAN1 / MCU -> USART2 -> COM4 '
|
||||
'上述是静态及软件编解码验证,另校验两份 DBC 的速率、相同信号布局及源码 / CubeMX 位时序一致。'
|
||||
'v2.1 的 CAN2 250 kbit/s 固件已编译,尚未烧录或实物验证。'
|
||||
'之前 v2.0 的 CAN 盒 -> 实物 AMI CAN1 / MCU -> USART2 -> COM4 '
|
||||
'-> 官方 HMI 调试器的实际更新和六个任务回传见 [联调报告](SDC_BMS联调报告.md)。'
|
||||
'物理串口屏、CAN2 接线、真实 SDC/BMS 节点和车辆执行动作尚未实测;'
|
||||
'文档生成和 DBC 解析不替代这些测试。', '',
|
||||
'修改 AMI.dbc 后运行 `python scripts/export_dbc_docs.py` 更新本文;'
|
||||
'修改 AMI.dbc 后运行 `python scripts/export_dbc_docs.py` 更新 CAN2 DBC 和本文;'
|
||||
'固件字节布局、DBC 与此文档需同时保持一致。', '',
|
||||
]
|
||||
return '\n'.join(lines)
|
||||
@@ -140,10 +168,12 @@ def main():
|
||||
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')
|
||||
assert CAN2_OUTPUT.read_text(encoding='ascii') == can2_dbc_text(), 'CAN2 DBC is stale'
|
||||
print('DBC documentation and CAN2 DBC match AMI.dbc')
|
||||
else:
|
||||
CAN2_OUTPUT.write_text(can2_dbc_text(), encoding='ascii', newline='\n')
|
||||
OUTPUT.write_text(text, encoding='utf-8', newline='\n')
|
||||
print('Generated docs/AMI_CAN_DBC说明.md')
|
||||
print('Generated CAN2 DBC and docs/AMI_CAN_DBC说明.md')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -10,11 +10,48 @@ from pathlib import Path
|
||||
import re
|
||||
|
||||
import cantools
|
||||
from export_dbc_docs import can2_dbc_text
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def verify_bit_timing():
|
||||
ioc = dict(line.split('=', 1) for line in
|
||||
(ROOT / 'firmware/AMI_Controller.ioc').read_text().splitlines() if '=' in line)
|
||||
source = (ROOT / 'firmware/Core/Src/fdcan.c').read_text()
|
||||
clock_source = (ROOT / 'firmware/Core/Src/main.c').read_text()
|
||||
for setting in ('RCC_HSI_DIV1', 'RCC_PLLM_DIV1', 'RCC_PLLR_DIV4', 'RCC_HCLK_DIV1'):
|
||||
assert setting in clock_source
|
||||
assert re.search(r'PLL\.PLLN\s*=\s*16;', clock_source)
|
||||
assert 'RCC_FDCANCLKSOURCE_PCLK1' in source
|
||||
clock = int(ioc['RCC.FDCANFreq_Value'])
|
||||
assert clock == 16000000 * 16 // 4 == int(ioc['RCC.APBFreq_Value'])
|
||||
result = {}
|
||||
for port, expected_rate, expected_prescaler in ((1, 500000, 8), (2, 250000, 16)):
|
||||
def value(field):
|
||||
found = re.search(rf'hfdcan{port}\.Init\.{field}\s*=\s*(\w+);', source)
|
||||
assert found, field
|
||||
return found.group(1)
|
||||
|
||||
assert value('ClockDivider') == 'FDCAN_CLOCK_DIV1'
|
||||
assert value('FrameFormat') == 'FDCAN_FRAME_CLASSIC'
|
||||
prescaler, seg1, seg2, sjw = (int(value(field)) for field in
|
||||
('NominalPrescaler', 'NominalTimeSeg1',
|
||||
'NominalTimeSeg2', 'NominalSyncJumpWidth'))
|
||||
assert (prescaler, seg1, seg2, sjw) == (expected_prescaler, 13, 2, 1)
|
||||
rate = clock // prescaler // (1 + seg1 + seg2)
|
||||
near((1 + seg1) / (1 + seg1 + seg2), 0.875)
|
||||
assert rate == expected_rate == int(ioc[f'FDCAN{port}.CalculateBaudRateNominal'])
|
||||
for field in ('NominalPrescaler', 'NominalTimeSeg1', 'NominalTimeSeg2',
|
||||
'DataPrescaler', 'DataTimeSeg1', 'DataTimeSeg2'):
|
||||
assert int(value(field)) == int(ioc[f'FDCAN{port}.{field}'])
|
||||
near(float(ioc[f'FDCAN{port}.CalculateTimeBitNominal']), 1e9 / rate)
|
||||
near(float(ioc[f'FDCAN{port}.CalculateTimeQuantumNominal']), 1e9 * prescaler / clock)
|
||||
result[f'CAN{port}'] = rate
|
||||
return result
|
||||
|
||||
|
||||
def near(actual, expected):
|
||||
assert math.isclose(actual, expected, rel_tol=0, abs_tol=1e-8), (actual, expected)
|
||||
|
||||
@@ -24,6 +61,12 @@ def main():
|
||||
parser.add_argument('--sdc-dbc', type=Path)
|
||||
args = parser.parse_args()
|
||||
db = cantools.database.load_file(ROOT / 'AMI.dbc', strict=True)
|
||||
db2 = cantools.database.load_file(ROOT / 'AMI_CAN2_250k.dbc', strict=True)
|
||||
assert (ROOT / 'AMI_CAN2_250k.dbc').read_text(encoding='ascii') == can2_dbc_text()
|
||||
assert db2.dbc.attributes['Baudrate'].value == 250000
|
||||
assert db.dbc.attributes['AMIPort'].value == 'CAN1'
|
||||
assert db2.dbc.attributes['AMIPort'].value == 'CAN2'
|
||||
bitrates = verify_bit_timing()
|
||||
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)}
|
||||
@@ -58,6 +101,7 @@ def main():
|
||||
assert task == {'Protocol_Version':1,'Task_ID':2,'Request_Sequence':0,
|
||||
'Request_Source':1,'MCU_Uptime':1000}
|
||||
request = db.get_message_by_frame_id(0x300)
|
||||
request2 = db2.get_message_by_frame_id(0x300)
|
||||
assert request.encode(task) == bytes.fromhex('01 02 00 01 E8 03 00 00')
|
||||
for task_id in range(1,7):
|
||||
for seq in (0,255):
|
||||
@@ -65,6 +109,8 @@ def main():
|
||||
payload=request.encode(data)
|
||||
assert payload == bytes([1,task_id,seq,1,255,255,255,255])
|
||||
assert request.decode(payload,decode_choices=False) == data
|
||||
assert request2.encode(data) == payload
|
||||
assert request2.decode(payload,decode_choices=False) == data
|
||||
assert set(request.get_signal_by_name('Task_ID').choices) == set(range(1,7))
|
||||
|
||||
for raw, volts in [('08 20',8.2),('0C 30',12.3),('88 31',12.68),('A4 38',14.5),('FF FF',65.535)]:
|
||||
@@ -137,8 +183,16 @@ def main():
|
||||
assert all(getattr(a,key)==getattr(b,key) for key in fields),(ident,a,b)
|
||||
source_check=True
|
||||
|
||||
for check in checks:
|
||||
ident = int(check['id'], 16)
|
||||
payload = bytes.fromhex(check['data'])
|
||||
for decode_choices in (False, True):
|
||||
assert db.decode_message(ident, payload, decode_choices=decode_choices) == \
|
||||
db2.decode_message(ident, payload, decode_choices=decode_choices)
|
||||
print(json.dumps({'result':'passed','messages':len(db.messages),'signals':33,
|
||||
'known_payload_checks':len(checks),'all_task_ids_and_counter_edges':True,
|
||||
'ports_bitrate':bitrates,'both_dbc_layouts_and_payloads_match':True,
|
||||
'firmware_and_ioc_bit_timing_match':True,
|
||||
'firmware_ids_match':True,'sdc_source_layout_match':source_check,
|
||||
'cantools_version':cantools.__version__},indent=2))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user