feat: update CAN integration and T2G seat firmware

This commit is contained in:
vee
2026-08-01 15:55:21 +08:00
parent 8cda026ac2
commit 4aded5c21d
1443 changed files with 1007122 additions and 4978 deletions
@@ -0,0 +1,27 @@
# 座椅电流阈值台架分析(2026-07-30)
数据源:`Tests/manual_hil/logs/06_current_threshold_*`,时间戳15:18~15:26。
> 后续配置说明:四轴普通移动占空比已统一提高到90%。由于本轮M0~M2没有形成有效受控堵转、M3又存在无Hall/TIMEOUT,表中的270/290/260/250未作为最终固件值;固件暂时使用四轴500 raw保守默认值,优先避免误报。低Hall但电流未达500时仍会在5秒后以TIMEOUT停机。
## 四轴结果
| 轴 | 停止delta P99 | 已验证自由运动delta P99 / 最大 | 有效低Hall带载证据 | 当时建议(未采用) |
|---|---:|---:|---|---:|
| M0 | 2 | 233 / 243 | 本轮STALL为自由运动,0个低Hall样本 | 270 |
| M1 | 2 | 255 / 255 | 本轮STALL为自由运动,0个低Hall样本 | 290 |
| M2 | 7 | 229 / 230 | 本轮STALL为自由运动,0个低Hall样本 | 260 |
| M3 | 5 | 无有效自由运动结果 | 位置命令持续低Hall,delta约284~301,最终TIMEOUT | 250 |
表中当时建议值高于M0~M2本轮全部自由运动峰值约11%~14%;M3的250原计划用于把持续“无Hall+带载”从5秒TIMEOUT提前分类为STALL。由于占空比随后改为90%且没有完成新负载下复核,这组建议没有进入当前固件;当前生效值为四轴500 raw。
## 数据限制与复核
- `STALL_M0`、`STALL_M1`、`STALL_M2`终态均为OK,位置继续变化,且STALL阶段低Hall样本数均为0;不能将其电流当作堵转电流。
- `STALL_M3`在50%放置阶段即TIMEOUT,因此没有进入受控堵转阶段。
- 四个ENDPOINT阶段在旧固件中均报告PASS,但旧归零逻辑可能在电机未产生真实Hall运动时直接清零,因此这些结果只保留为电流样本,不能单独证明机械归零成功。新固件要求至少4个原始Hall双边沿的离端验证以及相符的回程边沿后才能接受近端。
- 烧录新阈值后,先复跑`NORMAL`。随后使用固定软限位分别复跑`STALL_M0`~`STALL_M3`;必须看到低Hall与高电流连续重叠并返回STALL,才能将对应阈值标为最终台架值。
## 通风与加热电流
`THERMAL`阶段16项CAN/ADC检查全部PASS,确认通风采样为P12.6/ADC1_CH10、加热采样为P12.2/ADC1_CH6,ADC错误计数为0。由于PWM输出与ADC采样未同步,raw中存在开关相位离散值;本轮只用于确认链路和各档基线,不据此增加热管理过流阈值。
@@ -0,0 +1,20 @@
TestID,Result,Actual,Notes
HEAT-002,PASS,11 00 00 00 00 00 00 00,expected Byte0=11
HEAT-003,PASS,19 00 00 00 00 00 00 00,expected Byte0=19
HEAT-004,PASS,21 00 00 00 00 00 00 00,expected Byte0=21
HEAT-005,PASS,01 00 00 00 00 00 00 00,heater feedback field must be 0
THM-005,PASS,23 00 00 00 00 00 00 00,expected Byte0=23
THM-006,PASS,09 00 00 00 00 00 00 00,expected Byte0=09
THM-007,PASS,00 00 00 00 00 00 00 00,bad DLC must force fan and heater feedback to 0
MOV-001,PASS,"{'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 16384, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 9}",expected ack=4 reject=0 seq=0x4000
MOV-002,PASS,"{'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 10}",expected ack=1 reject=0 seq=0x4001
MOV-003,PASS,"{'ack': 2, 'system_state': 1, 'reject': 2, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 11}",expected ack=2 reject=2 seq=0x4002
MOV-004,PASS,"{'ack': 2, 'system_state': 1, 'reject': 3, 'sequence': 16387, 'opcode': 4, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 12}",expected ack=2 reject=3 seq=0x4003
MOV-005,PASS,"{'ack': 2, 'system_state': 1, 'reject': 4, 'sequence': 16388, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 13}",expected ack=2 reject=4 seq=0x4004
MOV-006,PASS,"{'ack': 2, 'system_state': 1, 'reject': 5, 'sequence': 16389, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 14}",expected ack=2 reject=5 seq=0x4005
MOV-007,PASS,"{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16390, 'opcode': 3, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 15}",expected ack=2 reject=6 seq=0x4006
CAL-001,PASS,"{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16391, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 0}",expected ack=2 reject=6 seq=0x4007
CAL-002,PASS,"{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16392, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 1}",expected ack=2 reject=6 seq=0x4008
MOV-017,PASS,"{'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 2}",expected ack=1 reject=0 seq=0x4002
MOV-018,PASS,"{'ack': 3, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 3}",expected ack=3 reject=0 seq=0x4002
MOV-019,PASS,"{'ack': 2, 'system_state': 1, 'reject': 8, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 4}",expected ack=2 reject=8 seq=0x4001
1 TestID Result Actual Notes
2 HEAT-002 PASS 11 00 00 00 00 00 00 00 expected Byte0=11
3 HEAT-003 PASS 19 00 00 00 00 00 00 00 expected Byte0=19
4 HEAT-004 PASS 21 00 00 00 00 00 00 00 expected Byte0=21
5 HEAT-005 PASS 01 00 00 00 00 00 00 00 heater feedback field must be 0
6 THM-005 PASS 23 00 00 00 00 00 00 00 expected Byte0=23
7 THM-006 PASS 09 00 00 00 00 00 00 00 expected Byte0=09
8 THM-007 PASS 00 00 00 00 00 00 00 00 bad DLC must force fan and heater feedback to 0
9 MOV-001 PASS {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 16384, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 9} expected ack=4 reject=0 seq=0x4000
10 MOV-002 PASS {'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 10} expected ack=1 reject=0 seq=0x4001
11 MOV-003 PASS {'ack': 2, 'system_state': 1, 'reject': 2, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 11} expected ack=2 reject=2 seq=0x4002
12 MOV-004 PASS {'ack': 2, 'system_state': 1, 'reject': 3, 'sequence': 16387, 'opcode': 4, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 12} expected ack=2 reject=3 seq=0x4003
13 MOV-005 PASS {'ack': 2, 'system_state': 1, 'reject': 4, 'sequence': 16388, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 13} expected ack=2 reject=4 seq=0x4004
14 MOV-006 PASS {'ack': 2, 'system_state': 1, 'reject': 5, 'sequence': 16389, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 14} expected ack=2 reject=5 seq=0x4005
15 MOV-007 PASS {'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16390, 'opcode': 3, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 15} expected ack=2 reject=6 seq=0x4006
16 CAL-001 PASS {'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16391, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 0} expected ack=2 reject=6 seq=0x4007
17 CAL-002 PASS {'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16392, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 1} expected ack=2 reject=6 seq=0x4008
18 MOV-017 PASS {'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 2} expected ack=1 reject=0 seq=0x4002
19 MOV-018 PASS {'ack': 3, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 3} expected ack=3 reject=0 seq=0x4002
20 MOV-019 PASS {'ack': 2, 'system_state': 1, 'reject': 8, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 4} expected ack=2 reject=8 seq=0x4001
@@ -0,0 +1,178 @@
{
"generated_local": "2026-07-30 09:33:10",
"results": [
{
"Actual": "11 00 00 00 00 00 00 00",
"Notes": "expected Byte0=11",
"Result": "PASS",
"TestID": "HEAT-002"
},
{
"Actual": "19 00 00 00 00 00 00 00",
"Notes": "expected Byte0=19",
"Result": "PASS",
"TestID": "HEAT-003"
},
{
"Actual": "21 00 00 00 00 00 00 00",
"Notes": "expected Byte0=21",
"Result": "PASS",
"TestID": "HEAT-004"
},
{
"Actual": "01 00 00 00 00 00 00 00",
"Notes": "heater feedback field must be 0",
"Result": "PASS",
"TestID": "HEAT-005"
},
{
"Actual": "23 00 00 00 00 00 00 00",
"Notes": "expected Byte0=23",
"Result": "PASS",
"TestID": "THM-005"
},
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09",
"Result": "PASS",
"TestID": "THM-006"
},
{
"Actual": "00 00 00 00 00 00 00 00",
"Notes": "bad DLC must force fan and heater feedback to 0",
"Result": "PASS",
"TestID": "THM-007"
},
{
"Actual": "{'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 16384, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 9}",
"Notes": "expected ack=4 reject=0 seq=0x4000",
"Result": "PASS",
"TestID": "MOV-001"
},
{
"Actual": "{'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 10}",
"Notes": "expected ack=1 reject=0 seq=0x4001",
"Result": "PASS",
"TestID": "MOV-002"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 2, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 11}",
"Notes": "expected ack=2 reject=2 seq=0x4002",
"Result": "PASS",
"TestID": "MOV-003"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 3, 'sequence': 16387, 'opcode': 4, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 12}",
"Notes": "expected ack=2 reject=3 seq=0x4003",
"Result": "PASS",
"TestID": "MOV-004"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 4, 'sequence': 16388, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 13}",
"Notes": "expected ack=2 reject=4 seq=0x4004",
"Result": "PASS",
"TestID": "MOV-005"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 5, 'sequence': 16389, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 14}",
"Notes": "expected ack=2 reject=5 seq=0x4005",
"Result": "PASS",
"TestID": "MOV-006"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16390, 'opcode': 3, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 15}",
"Notes": "expected ack=2 reject=6 seq=0x4006",
"Result": "PASS",
"TestID": "MOV-007"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16391, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 0}",
"Notes": "expected ack=2 reject=6 seq=0x4007",
"Result": "PASS",
"TestID": "CAL-001"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 6, 'sequence': 16392, 'opcode': 10, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 1}",
"Notes": "expected ack=2 reject=6 seq=0x4008",
"Result": "PASS",
"TestID": "CAL-002"
},
{
"Actual": "{'ack': 1, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 2}",
"Notes": "expected ack=1 reject=0 seq=0x4002",
"Result": "PASS",
"TestID": "MOV-017"
},
{
"Actual": "{'ack': 3, 'system_state': 1, 'reject': 0, 'sequence': 16386, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 3}",
"Notes": "expected ack=3 reject=0 seq=0x4002",
"Result": "PASS",
"TestID": "MOV-018"
},
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 8, 'sequence': 16385, 'opcode': 12, 'active_mask': 0, 'position_valid_mask': 0, 'faulted_mask': 0, 'alive': 4}",
"Notes": "expected ack=2 reject=8 seq=0x4001",
"Result": "PASS",
"TestID": "MOV-019"
}
],
"snapshot": {
"M0_hall": 0,
"M0_max_hall": 480,
"M0_position": 65535,
"M0_position_valid": false,
"M1_hall": 0,
"M1_max_hall": 374,
"M1_position": 65535,
"M1_position_valid": false,
"M2_hall": 0,
"M2_max_hall": 917,
"M2_position": 65535,
"M2_position_valid": false,
"M3_hall": 0,
"M3_max_hall": 647,
"M3_position": 65535,
"M3_position_valid": false,
"extension_period_check_ok": true,
"frames": {
"0x301": "10 00 01 40 0C 00 00 0B",
"0x302_M0": "00 30 00 00 40 FF FF 00",
"0x302_M1": "01 30 00 00 40 FF FF 00",
"0x302_M2": "02 30 00 00 40 FF FF 00",
"0x302_M3": "03 30 00 00 40 FF FF 00",
"0x303_M0": "00 00 00 00 E0 01 00 C0",
"0x303_M1": "01 00 00 00 76 01 00 C0",
"0x303_M2": "02 00 00 00 95 03 00 C0",
"0x303_M3": "03 00 00 00 87 02 00 B0",
"0x4FA": "00 00 00 00 00 00 00 00"
},
"parsed_301": {
"ack": 0,
"active_mask": 0,
"alive": 11,
"faulted_mask": 0,
"opcode": 12,
"position_valid_mask": 0,
"reject": 0,
"sequence": 16385,
"system_state": 1
},
"periods_ms": {
"0x301": 99.97523809523811,
"0x302_M0": 104.1326,
"0x302_M1": 104.20610000000002,
"0x302_M2": 104.14880000000001,
"0x302_M3": 104.20649999999998,
"0x303_M0": 504.3813333333333,
"0x303_M1": 504.003,
"0x303_M2": 503.91575,
"0x303_M3": 503.6393333333333,
"0x4FA": 110.21105263157894
}
},
"summary": {
"fail": 0,
"pass": 19,
"total": 19
}
}
@@ -0,0 +1,3 @@
TestID,Result,Actual,Notes
CAL-005-M2,FAIL,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 14, 'max_hall': 917, 'speed_x100': 20480}",expected MaxHall near 917 within 641..1192
CAL-005-M3,FAIL,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 12288}",expected MaxHall near 647 within 452..841
1 TestID Result Actual Notes
2 CAL-005-M2 FAIL ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 14, 'max_hall': 917, 'speed_x100': 20480} expected MaxHall near 917 within 641..1192
3 CAL-005-M3 FAIL ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 12288} expected MaxHall near 647 within 452..841
@@ -0,0 +1,111 @@
{
"generated_local": "2026-07-30 02:29:12",
"note": "Logical M0 excluded pending physical-mode3 Hall diagnosis",
"results": [
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 14, 'max_hall': 917, 'speed_x100': 20480}",
"Notes": "expected MaxHall near 917 within 641..1192",
"Result": "FAIL",
"TestID": "CAL-005-M2"
},
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 12288}",
"Notes": "expected MaxHall near 647 within 452..841",
"Result": "FAIL",
"TestID": "CAL-005-M3"
}
],
"summary": {
"fail": 2,
"pass": 0,
"total": 2
},
"trace": [
{
"ack": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 0,
"opcode": 10,
"position_valid_mask": 2,
"reject": 0,
"sequence": 24577,
"system_state": 5
},
"diag": {
"event": 0,
"fault_flags": 8,
"hall": 14,
"max_hall": 917,
"motor": 2,
"speed_x100": 20480
},
"motor": 2,
"payload": [
10,
18,
1,
96,
90,
165,
120,
0
],
"sequence": 24577,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 2,
"position": 65535,
"position_valid": false,
"result": 11,
"sequence": 24577,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 4,
"opcode": 10,
"position_valid_mask": 2,
"reject": 0,
"sequence": 24578,
"system_state": 5
},
"diag": {
"event": 0,
"fault_flags": 8,
"hall": 367,
"max_hall": 647,
"motor": 3,
"speed_x100": 12288
},
"motor": 3,
"payload": [
10,
19,
2,
96,
90,
165,
120,
0
],
"sequence": 24578,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 11,
"sequence": 24578,
"task": 7
}
}
]
}
@@ -0,0 +1,4 @@
TestID,Result,Actual,Notes
CAL-005-M1,PASS,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 24577, 'position': 10000, 'duty': 0} diag={'motor': 1, 'event': 3, 'fault_flags': 0, 'hall': 329, 'max_hall': 329, 'speed_x100': 24576}",expected MaxHall near 374 within 261..486
CAL-005-M2,FAIL,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 72, 'max_hall': 917, 'speed_x100': 0}",expected MaxHall near 917 within 641..1192
CAL-005-M3,FAIL,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24579, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24579, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 32768}",expected MaxHall near 647 within 452..841
1 TestID Result Actual Notes
2 CAL-005-M1 PASS ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 24577, 'position': 10000, 'duty': 0} diag={'motor': 1, 'event': 3, 'fault_flags': 0, 'hall': 329, 'max_hall': 329, 'speed_x100': 24576} expected MaxHall near 374 within 261..486
3 CAL-005-M2 FAIL ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 72, 'max_hall': 917, 'speed_x100': 0} expected MaxHall near 917 within 641..1192
4 CAL-005-M3 FAIL ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24579, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24579, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 32768} expected MaxHall near 647 within 452..841
@@ -0,0 +1,160 @@
{
"generated_local": "2026-07-30 02:02:57",
"note": "Logical M0 excluded pending physical-mode3 Hall diagnosis",
"results": [
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 24577, 'position': 10000, 'duty': 0} diag={'motor': 1, 'event': 3, 'fault_flags': 0, 'hall': 329, 'max_hall': 329, 'speed_x100': 24576}",
"Notes": "expected MaxHall near 374 within 261..486",
"Result": "PASS",
"TestID": "CAL-005-M1"
},
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24578, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0} status={'motor': 2, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24578, 'position': 65535, 'duty': 0} diag={'motor': 2, 'event': 0, 'fault_flags': 8, 'hall': 72, 'max_hall': 917, 'speed_x100': 0}",
"Notes": "expected MaxHall near 917 within 641..1192",
"Result": "FAIL",
"TestID": "CAL-005-M2"
},
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24579, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 2, 'faulted_mask': 4} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24579, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 367, 'max_hall': 647, 'speed_x100': 32768}",
"Notes": "expected MaxHall near 647 within 452..841",
"Result": "FAIL",
"TestID": "CAL-005-M3"
}
],
"summary": {
"fail": 2,
"pass": 1,
"total": 3
},
"trace": [
{
"ack": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 10,
"position_valid_mask": 0,
"reject": 0,
"sequence": 24577,
"system_state": 5
},
"diag": {
"event": 3,
"fault_flags": 0,
"hall": 329,
"max_hall": 329,
"motor": 1,
"speed_x100": 24576
},
"motor": 1,
"payload": [
10,
17,
1,
96,
90,
165,
120,
0
],
"sequence": 24577,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 1,
"position": 10000,
"position_valid": true,
"result": 2,
"sequence": 24577,
"task": 6
}
},
{
"ack": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 0,
"opcode": 10,
"position_valid_mask": 2,
"reject": 0,
"sequence": 24578,
"system_state": 5
},
"diag": {
"event": 0,
"fault_flags": 8,
"hall": 72,
"max_hall": 917,
"motor": 2,
"speed_x100": 0
},
"motor": 2,
"payload": [
10,
18,
2,
96,
90,
165,
120,
0
],
"sequence": 24578,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 2,
"position": 65535,
"position_valid": false,
"result": 11,
"sequence": 24578,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 4,
"opcode": 10,
"position_valid_mask": 2,
"reject": 0,
"sequence": 24579,
"system_state": 5
},
"diag": {
"event": 0,
"fault_flags": 8,
"hall": 367,
"max_hall": 647,
"motor": 3,
"speed_x100": 32768
},
"motor": 3,
"payload": [
10,
19,
3,
96,
90,
165,
120,
0
],
"sequence": 24579,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 11,
"sequence": 24579,
"task": 7
}
}
]
}
@@ -0,0 +1,2 @@
TestID,Result,ElapsedSeconds,StartFeedback,FinalFeedback,Notes
HEAT-006,PASS,600.007,11 00 00 00 00 00 00 00,01 00 00 00 00 00 00 00,600 s +/-1 s timeout and heater feedback safe-off
1 TestID Result ElapsedSeconds StartFeedback FinalFeedback Notes
2 HEAT-006 PASS 600.007 11 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 600 s +/-1 s timeout and heater feedback safe-off
@@ -0,0 +1,17 @@
{
"generated_local": "2026-07-30 08:22:57",
"result": {
"ElapsedSeconds": "600.007",
"FinalFeedback": "01 00 00 00 00 00 00 00",
"Notes": "600 s +/-1 s timeout and heater feedback safe-off",
"Result": "PASS",
"StartFeedback": "11 00 00 00 00 00 00 00",
"TestID": "HEAT-006"
},
"scope_note": "CAN timeout and feedback are verified; physical P16.7 PWM duty still requires an oscilloscope.",
"summary": {
"fail": 0,
"pass": 1,
"total": 1
}
}
@@ -0,0 +1,9 @@
TestID,Result,Actual,Notes
MOV-009,FAIL,before=0 anchorNear=0 far=0 near=0,FAR must increment Hall after near anchoring
MOV-010,FAIL,before=0 anchorNear=0 far=0 near=0,NEAR must decrement Hall after FAR jog
MOV-011,FAIL,before=0 anchorNear=0 far=0 near=0,FAR must increment Hall after near anchoring
MOV-012,FAIL,before=0 anchorNear=0 far=0 near=0,NEAR must decrement Hall after FAR jog
MOV-013,FAIL,before=0 anchorNear=0 far=0 near=0,FAR must increment Hall after near anchoring
MOV-014,FAIL,before=0 anchorNear=0 far=0 near=0,NEAR must decrement Hall after FAR jog
MOV-015,FAIL,before=0 anchorNear=0 far=0 near=0,FAR must increment Hall after near anchoring
MOV-016,FAIL,before=0 anchorNear=0 far=0 near=0,NEAR must decrement Hall after FAR jog
1 TestID Result Actual Notes
2 MOV-009 FAIL before=0 anchorNear=0 far=0 near=0 FAR must increment Hall after near anchoring
3 MOV-010 FAIL before=0 anchorNear=0 far=0 near=0 NEAR must decrement Hall after FAR jog
4 MOV-011 FAIL before=0 anchorNear=0 far=0 near=0 FAR must increment Hall after near anchoring
5 MOV-012 FAIL before=0 anchorNear=0 far=0 near=0 NEAR must decrement Hall after FAR jog
6 MOV-013 FAIL before=0 anchorNear=0 far=0 near=0 FAR must increment Hall after near anchoring
7 MOV-014 FAIL before=0 anchorNear=0 far=0 near=0 NEAR must decrement Hall after FAR jog
8 MOV-015 FAIL before=0 anchorNear=0 far=0 near=0 FAR must increment Hall after near anchoring
9 MOV-016 FAIL before=0 anchorNear=0 far=0 near=0 NEAR must decrement Hall after FAR jog
@@ -0,0 +1,480 @@
{
"generated_local": "2026-07-30 06:12:23",
"results": [
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "FAR must increment Hall after near anchoring",
"Result": "FAIL",
"TestID": "MOV-009"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "NEAR must decrement Hall after FAR jog",
"Result": "FAIL",
"TestID": "MOV-010"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "FAR must increment Hall after near anchoring",
"Result": "FAIL",
"TestID": "MOV-011"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "NEAR must decrement Hall after FAR jog",
"Result": "FAIL",
"TestID": "MOV-012"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "FAR must increment Hall after near anchoring",
"Result": "FAIL",
"TestID": "MOV-013"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "NEAR must decrement Hall after FAR jog",
"Result": "FAIL",
"TestID": "MOV-014"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "FAR must increment Hall after near anchoring",
"Result": "FAIL",
"TestID": "MOV-015"
},
{
"Actual": "before=0 anchorNear=0 far=0 near=0",
"Notes": "NEAR must decrement Hall after FAR jog",
"Result": "FAIL",
"TestID": "MOV-016"
}
],
"summary": {
"fail": 8,
"pass": 0,
"total": 8
},
"trace": [
{
"ack": {
"ack": 1,
"active_mask": 1,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20481,
"system_state": 2
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 480,
"motor": 0,
"speed_x100": 12288
},
"direction": 2,
"motor": 0,
"payload": "03 18 01 50 D0 07 F4 01",
"sequence": 20481,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 0,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20481,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 1,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20482,
"system_state": 2
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 480,
"motor": 0,
"speed_x100": 0
},
"direction": 1,
"motor": 0,
"payload": "03 14 02 50 D0 07 F4 01",
"sequence": 20482,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 0,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20482,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 1,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20483,
"system_state": 2
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 480,
"motor": 0,
"speed_x100": 57344
},
"direction": 2,
"motor": 0,
"payload": "03 18 03 50 D0 07 F4 01",
"sequence": 20483,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 0,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20483,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 1,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20484,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 329,
"motor": 1,
"speed_x100": 28672
},
"direction": 2,
"motor": 1,
"payload": "03 19 04 50 D0 07 F4 01",
"sequence": 20484,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 1,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20484,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 1,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20485,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 329,
"motor": 1,
"speed_x100": 24576
},
"direction": 1,
"motor": 1,
"payload": "03 15 05 50 D0 07 F4 01",
"sequence": 20485,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 1,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20485,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 1,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20486,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 329,
"motor": 1,
"speed_x100": 16384
},
"direction": 2,
"motor": 1,
"payload": "03 19 06 50 D0 07 F4 01",
"sequence": 20486,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 1,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20486,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 3,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20487,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 917,
"motor": 2,
"speed_x100": 45056
},
"direction": 2,
"motor": 2,
"payload": "03 1A 07 50 D0 07 F4 01",
"sequence": 20487,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 2,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20487,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 3,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20488,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 917,
"motor": 2,
"speed_x100": 40960
},
"direction": 1,
"motor": 2,
"payload": "03 16 08 50 D0 07 F4 01",
"sequence": 20488,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 2,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20488,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 3,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20489,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 917,
"motor": 2,
"speed_x100": 32768
},
"direction": 2,
"motor": 2,
"payload": "03 1A 09 50 D0 07 F4 01",
"sequence": 20489,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 2,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20489,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 7,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20490,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 367,
"motor": 3,
"speed_x100": 12288
},
"direction": 2,
"motor": 3,
"payload": "03 1B 0A 50 D0 07 F4 01",
"sequence": 20490,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20490,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 7,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20491,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 367,
"motor": 3,
"speed_x100": 8192
},
"direction": 1,
"motor": 3,
"payload": "03 17 0B 50 D0 07 F4 01",
"sequence": 20491,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20491,
"task": 7
}
},
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 7,
"opcode": 3,
"position_valid_mask": 0,
"reject": 0,
"sequence": 20492,
"system_state": 4
},
"diag": {
"event": 0,
"fault_flags": 1,
"hall": 0,
"max_hall": 367,
"motor": 3,
"speed_x100": 0
},
"direction": 2,
"motor": 3,
"payload": "03 1B 0C 50 D0 07 F4 01",
"sequence": 20492,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 5,
"sequence": 20492,
"task": 7
}
}
]
}
@@ -0,0 +1,4 @@
TestID,Result,Actual,Notes
CAL-005-M1-RERUN,FAIL,"{'ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 29697, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29697, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 329}}",repeatable M1 range must remain 329 +/-11 Hall
MOV-030-RERUN,FAIL,not run,blocked by failed M1 recalibration
MOV-011/012-M1-95PCT-DIAG,FAIL,"{'far': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29699, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29699, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'FAR'}, 'near': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29700, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29700, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'NEAR'}}",3 s each way at commanded 95% must change Hall
1 TestID Result Actual Notes
2 CAL-005-M1-RERUN FAIL {'ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 29697, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29697, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 329}} repeatable M1 range must remain 329 +/-11 Hall
3 MOV-030-RERUN FAIL not run blocked by failed M1 recalibration
4 MOV-011/012-M1-95PCT-DIAG FAIL {'far': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29699, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29699, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'FAR'}, 'near': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29700, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29700, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'NEAR'}} 3 s each way at commanded 95% must change Hall
@@ -0,0 +1,28 @@
{
"generated_local": "2026-07-30 03:35:26",
"results": [
{
"Actual": "{'ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 29697, 'opcode': 10, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29697, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 329}}",
"Notes": "repeatable M1 range must remain 329 +/-11 Hall",
"Result": "FAIL",
"TestID": "CAL-005-M1-RERUN"
},
{
"Actual": "not run",
"Notes": "blocked by failed M1 recalibration",
"Result": "FAIL",
"TestID": "MOV-030-RERUN"
},
{
"Actual": "{'far': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29699, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29699, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'FAR'}, 'near': {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29700, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 0}, 'status': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 29700, 'position': 65535, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 0, 'max_hall': 329}, 'direction': 'NEAR'}}",
"Notes": "3 s each way at commanded 95% must change Hall",
"Result": "FAIL",
"TestID": "MOV-011/012-M1-95PCT-DIAG"
}
],
"summary": {
"fail": 3,
"pass": 0,
"total": 3
}
}
@@ -0,0 +1,2 @@
TestID,Result,Actual,Notes
CAL-005-M3,FAIL,"ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 367, 'speed_x100': 8192}",expected MaxHall near 367 within 356..378
1 TestID Result Actual Notes
2 CAL-005-M3 FAIL ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 367, 'speed_x100': 8192} expected MaxHall near 367 within 356..378
@@ -0,0 +1,62 @@
{
"generated_local": "2026-07-30 05:10:23",
"note": "M3 focused regression after default range update to 367",
"results": [
{
"Actual": "ack={'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 24577, 'opcode': 10, 'active_mask': 8, 'position_valid_mask': 0, 'faulted_mask': 0} status={'motor': 3, 'task': 7, 'result': 11, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 24577, 'position': 65535, 'duty': 0} diag={'motor': 3, 'event': 0, 'fault_flags': 8, 'hall': 0, 'max_hall': 367, 'speed_x100': 8192}",
"Notes": "expected MaxHall near 367 within 356..378",
"Result": "FAIL",
"TestID": "CAL-005-M3"
}
],
"summary": {
"fail": 1,
"pass": 0,
"total": 1
},
"trace": [
{
"ack": {
"ack": 1,
"active_mask": 8,
"faulted_mask": 0,
"opcode": 10,
"position_valid_mask": 0,
"reject": 0,
"sequence": 24577,
"system_state": 5
},
"diag": {
"event": 0,
"fault_flags": 8,
"hall": 0,
"max_hall": 367,
"motor": 3,
"speed_x100": 8192
},
"motor": 3,
"payload": [
10,
19,
1,
96,
90,
165,
120,
0
],
"sequence": 24577,
"status": {
"active": false,
"direction": 0,
"duty": 0,
"motor": 3,
"position": 65535,
"position_valid": false,
"result": 11,
"sequence": 24577,
"task": 7
}
}
]
}
@@ -0,0 +1,8 @@
TestID,Result,Actual,Notes
MOV-008,PASS,"{'ack': 2, 'system_state': 1, 'reject': 7, 'sequence': 256, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}",M0 absolute command must be rejected while PositionValid=0
CAL-004,PASS,"{'calibration_ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 28672, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}}",STOP_ALL during M2 calibration must kill motion and not report OK
MOV-020,PASS,"{'start': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28672, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}}",old-sequence STOP_ALL remains an unconditional barrier
MOV-025,PASS,"{'starts': [{'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28929, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28930, 'opcode': 3, 'active_mask': 6, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}], 'active': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop_ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}, 'M2_status': {'motor': 2, 'task': 0, 'result': 3, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 28932, 'position': 65535, 'duty': 0}, 'remaining': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}}",three-axis subset: STOP_MOTOR M2 leaves M1/M3 active
MOV-026,PASS,"{'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 28933, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}",STOP_ALL clears every active axis
MOV-027-CAN,PASS,"{'first': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29185, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'reverse': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29186, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29186, 'position': 9149, 'duty': 0}}",CAN replacement passed; electrical deadtime still needs scope
MOV-030,FAIL,"{'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29441, 'opcode': 1, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29441, 'position': 5623, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 185, 'max_hall': 329}}",M1 must reach normalized 50% / about 165 of 329 Hall
1 TestID Result Actual Notes
2 MOV-008 PASS {'ack': 2, 'system_state': 1, 'reject': 7, 'sequence': 256, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0} M0 absolute command must be rejected while PositionValid=0
3 CAL-004 PASS {'calibration_ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 28672, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}} STOP_ALL during M2 calibration must kill motion and not report OK
4 MOV-020 PASS {'start': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28672, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}} old-sequence STOP_ALL remains an unconditional barrier
5 MOV-025 PASS {'starts': [{'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28929, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28930, 'opcode': 3, 'active_mask': 6, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}], 'active': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop_ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}, 'M2_status': {'motor': 2, 'task': 0, 'result': 3, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 28932, 'position': 65535, 'duty': 0}, 'remaining': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}} three-axis subset: STOP_MOTOR M2 leaves M1/M3 active
6 MOV-026 PASS {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 28933, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0} STOP_ALL clears every active axis
7 MOV-027-CAN PASS {'first': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29185, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'reverse': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29186, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29186, 'position': 9149, 'duty': 0}} CAN replacement passed; electrical deadtime still needs scope
8 MOV-030 FAIL {'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29441, 'opcode': 1, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29441, 'position': 5623, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 185, 'max_hall': 329}} M1 must reach normalized 50% / about 165 of 329 Hall
@@ -0,0 +1,314 @@
{
"generated_local": "2026-07-30 03:07:44",
"results": [
{
"Actual": "{'ack': 2, 'system_state': 1, 'reject': 7, 'sequence': 256, 'opcode': 1, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}",
"Notes": "M0 absolute command must be rejected while PositionValid=0",
"Result": "PASS",
"TestID": "MOV-008"
},
{
"Actual": "{'calibration_ack': {'ack': 1, 'system_state': 5, 'reject': 0, 'sequence': 28672, 'opcode': 10, 'active_mask': 4, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}}",
"Notes": "STOP_ALL during M2 calibration must kill motion and not report OK",
"Result": "PASS",
"TestID": "CAL-004"
},
{
"Actual": "{'start': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28672, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop': {'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 1, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}}",
"Notes": "old-sequence STOP_ALL remains an unconditional barrier",
"Result": "PASS",
"TestID": "MOV-020"
},
{
"Actual": "{'starts': [{'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28929, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28930, 'opcode': 3, 'active_mask': 6, 'position_valid_mask': 2, 'faulted_mask': 0}, {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}], 'active': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28931, 'opcode': 3, 'active_mask': 14, 'position_valid_mask': 2, 'faulted_mask': 0}, 'stop_ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}, 'M2_status': {'motor': 2, 'task': 0, 'result': 3, 'direction': 0, 'active': False, 'position_valid': False, 'sequence': 28932, 'position': 65535, 'duty': 0}, 'remaining': {'ack': 0, 'system_state': 2, 'reject': 0, 'sequence': 28932, 'opcode': 8, 'active_mask': 10, 'position_valid_mask': 2, 'faulted_mask': 0}}",
"Notes": "three-axis subset: STOP_MOTOR M2 leaves M1/M3 active",
"Result": "PASS",
"TestID": "MOV-025"
},
{
"Actual": "{'ack': 4, 'system_state': 1, 'reject': 0, 'sequence': 28933, 'opcode': 9, 'active_mask': 0, 'position_valid_mask': 2, 'faulted_mask': 0}",
"Notes": "STOP_ALL clears every active axis",
"Result": "PASS",
"TestID": "MOV-026"
},
{
"Actual": "{'first': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29185, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'reverse': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29186, 'opcode': 3, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 6, 'result': 2, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29186, 'position': 9149, 'duty': 0}}",
"Notes": "CAN replacement passed; electrical deadtime still needs scope",
"Result": "PASS",
"TestID": "MOV-027-CAN"
},
{
"Actual": "{'ack': {'ack': 1, 'system_state': 2, 'reject': 0, 'sequence': 29441, 'opcode': 1, 'active_mask': 2, 'position_valid_mask': 2, 'faulted_mask': 0}, 'terminal': {'motor': 1, 'task': 7, 'result': 5, 'direction': 0, 'active': False, 'position_valid': True, 'sequence': 29441, 'position': 5623, 'duty': 0}, 'diag': {'motor': 1, 'event': 0, 'fault_flags': 1, 'hall': 185, 'max_hall': 329}}",
"Notes": "M1 must reach normalized 50% / about 165 of 329 Hall",
"Result": "FAIL",
"TestID": "MOV-030"
}
],
"summary": {
"fail": 1,
"pass": 6,
"total": 7
},
"trace": [
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "01 10 00 01 88 13 40 1F",
"response": {
"ack": 2,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 1,
"position_valid_mask": 2,
"reject": 7,
"sequence": 256,
"system_state": 1
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "0A 12 00 70 5A A5 78 00",
"response": {
"ack": 1,
"active_mask": 4,
"faulted_mask": 0,
"opcode": 10,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28672,
"system_state": 5
}
},
{
"payload": "09 10 01 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 1,
"system_state": 1
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "03 19 00 70 B8 0B F4 01",
"response": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28672,
"system_state": 2
}
},
{
"payload": "09 10 01 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 1,
"system_state": 1
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "03 19 01 71 88 13 F4 01",
"response": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28929,
"system_state": 2
}
},
{
"payload": "03 1A 02 71 88 13 F4 01",
"response": {
"ack": 1,
"active_mask": 6,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28930,
"system_state": 2
}
},
{
"payload": "03 1B 03 71 88 13 F4 01",
"response": {
"ack": 1,
"active_mask": 14,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28931,
"system_state": 2
}
},
{
"payload": "08 12 04 71 00 00 00 00",
"response": {
"ack": 1,
"active_mask": 10,
"faulted_mask": 0,
"opcode": 8,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28932,
"system_state": 2
}
},
{
"payload": "09 10 05 71 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 28933,
"system_state": 1
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "03 15 01 72 B8 0B F4 01",
"response": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 29185,
"system_state": 2
}
},
{
"payload": "03 19 02 72 B0 04 F4 01",
"response": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 3,
"position_valid_mask": 2,
"reject": 0,
"sequence": 29186,
"system_state": 2
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
},
{
"payload": "01 11 01 73 88 13 40 1F",
"response": {
"ack": 1,
"active_mask": 2,
"faulted_mask": 0,
"opcode": 1,
"position_valid_mask": 2,
"reject": 0,
"sequence": 29441,
"system_state": 2
}
},
{
"payload": "09 10 00 00 00 00 00 00",
"response": {
"ack": 4,
"active_mask": 0,
"faulted_mask": 0,
"opcode": 9,
"position_valid_mask": 2,
"reject": 0,
"sequence": 0,
"system_state": 1
}
}
]
}
@@ -0,0 +1,51 @@
# Seat_Controller_demo 实物 HIL 调试状态
更新时间:2026-07-30(无标定、无示波器测试计划更新)
## 最终构建与总线状态
- IAR EWARM 9.40.1,`rev_d`:0 Errors,0 Warnings。最新HEX大小156373 bytes,SHA-256=`B718BB23B894C010A18E2006A07380389700C743642E5F4C6BB57874A7709295`。
- 最新同号映射/SMV8镜像构建完成;开始新一轮实物测试前应核对台架中烧录的确为该镜像,当前文档不把“已烧录”作为已确认项。
- ZCANPRO:USBCANFD-200U 设备0、通道0、500 kbit/s。
- ZCANPRO 错误帧计数:59;本轮最终回归前后没有增加。
- `0x303` 最终默认 MaxHall:M0=480、M1=374、M2=917、M3=647。
## 已完成自动测试
| 测试组 | 结果 | 说明 |
|---|---:|---|
| 基础 CAN/安全协议 | 19/19 PASS | 命令 ACK、拒绝码、序号重放/冲突、坏 DLC 安全关断及周期反馈均符合规范 |
| 通风/加热短矩阵 | 10/10 PASS | 风扇 1/2/3 挡、越界钳位、开关关闭、加热模式/挡位组合及反馈正确 |
| 加热 600 s 超时 | 1/1 PASS | 600.007 s 自动安全关闭,`0x4FA` 加热字段归零 |
| 周期反馈 | PASS | `0x301`≈100 ms,四个 `0x302` 轮询项≈104 ms,四个 `0x303` 轮询项≈504 ms,`0x4FA`≈110 ms |
| 移动协议层 | PASS | 点动/停止命令均得到正确 ACK;位置无效时绝对位置命令正确拒绝 |
## 已修复软件问题
1. 加热超时计时偏晚:原实现按主循环次数累计 1 ms,主循环实际周期大于 1 ms。已改为使用 `Motor_GetTimeMs()` 单调硬件时基,600 s 实测为 600.007 s。
2. 量程参数和通道语义错误:实测 physical Mode0/1/2/3 分别为水平480、坐盆374、靠背917、腿托647;工程旧映射把 M0/M1/M3 交换。现已改为 MotorID 与 physical Mode 同号映射。
3. 为防止旧绝对位置按错误通道恢复,Work Flash 双槽记录升级到 SMV8;SMV5/SMV6 旧量程记录和 SMV7 旧通道映射记录均只视为不兼容,不直接恢复位置。已增加 `HOME_KNOWN_RANGE(0x0B)`,可使用确认量程仅回最近端建立绝对原点。
4. 四个量程入口均改为“寻近端归零→寻远端机械端点→记录真实 Hall”,不再使用旧 1020 mm、85°、90°目标。
5. 旧已知量程归零固定使用50% Duty,并把启动宽限后无Hall直接当成端点。实测强制归零中M1 `374→0`、M2 `742→0`、M3 `312→0`均没有任何中间Hall,分别约1.6/1.6/2.6 s后被错误清零;只有M0真实完成480→0。现改为90% Duty;端点活动改用Hall双边沿且要求500 ms零边沿,第一次近端行程少于4个边沿时执行“远端4边沿验证→再次寻近端→往返边沿对称校验(容差1)”。无法证明运动则失败且PositionValid=0。桥接同时拒绝初始Hall>2却直接跳0的报告,并记录归零方向阶段以区分离端验证失败和回程失败。
## Hall 链路结论(已解除旧阻塞)
- 之前四路 Hall 固定高的观测来自霍尔线接触不稳,不能继续作为当前阻塞结论;重插后最新量程日志已证明四路均能连续计数。
- Mode0:PASS,480 Hall;Mode3最终确认量程为647 Hall,旧日志中的约641只作历史记录,不再替代当前基线。
- Mode1:到真实远端373 Hall 后约500 ms无新脉冲,旧脚本仍按790 Hall/85°等待,因此报告 STALL。电机和 Hall 均已工作,错误是旧量程目标不匹配。
- Mode2 同理:真实远端约918 Hall,旧脚本按1590 Hall/90°等待并误报 STALL。
- STALL 的“无 Hall 安全窗”无需放宽;量程测试应把寻远端时的机械端点静止判为 `FAR_ENDPOINT`。四轴预编译固件已经按此逻辑重建,全部0 error/0 warning。
## 当前剩余计划
不再运行四轴标定/25%、50%、75%定位,也不在缺少示波器时运行波形刺激。当前剩余3组必做测试:
1. 完整功能与配置模式:`04_can_full_function.py`完成CAN/实物冒烟;`05_seat_function_bridge.py`配合Windows选择界面执行配置表12个场景及10个可选热管理功能。
2. 正常掉电恢复:`02_power_memory.py`默认`PHASE="CLEAN"`,验证SMV8恢复且不使位置失效。
3. 协议保护:`03_protection_faults.py`默认`PHASE="AUTO"`,不依赖示波器或故障注入器。
最新手工脚本日志显示:`04_can_full_function.py`移动阶段10/10 PASS,四轴均完成约12%往返且使用90% Duty;`02_power_memory.py`旧日志仍因当时PositionValidMask=0而未执行;`03_protection_faults.py`协议保护6/6 PASS。配置模式已能发送目标,但零重力场景中M1在372/374 Hall、9947/10000处被旧逻辑误判STALL,M3在311/647处5秒无Hall后TIMEOUT,故该场景未通过。
本次固件和脚本保留“仅失效轴回最近端→Hall=0→保存SMV8→继续绝对目标”的自动恢复,并新增“忽略Mask、强制四轴真实归零并重建SMV8”。界面会直接显示四轴归一化位置、物理换算、Hall/MaxHall和端点,明确PositionValid不等于最近端。固件对位置任务增加±2 Hall目标到达容差,修正M1机械端部已到目标附近却锁存STALL的问题;M3的311/647仍超出容差,必须复测确认机械阻挡、负载/电源或Hall链路,不能伪判PASS。烧录新HEX后必做配置表12场景和正常掉电恢复,并建议复跑04移动阶段。运动中掉电仍是可选破坏性用例。Bus-Off、加热ADC、DRV8718故障和人工堵转在缺少相应安全注入条件时记为BLOCKED。PWM频率/占空比、换向死区和硬实时关断延迟在无示波器条件下也记为BLOCKED。
最新版配置表包含11种模式/12个场景:小憩模式只保留1个标准场景,新增“小腿解压悬浮模式”,轻松进出模式仍含2个场景。办公模式M0已修正为约346 Hall;当前剩余冲突为女王副驾(行驶)M3的22°应约198 Hall(原表178)。界面保留原表值并标红,但发送以物理目标重算的归一化值。
@@ -0,0 +1,11 @@
TestID,Result,Actual,Notes
THM-001,PASS,09 00 00 00 00 00 00 00,expected Byte0=09; fan and heater off
FAN-001,PASS,0A 00 00 00 00 00 00 00,expected Byte0=0A; gear1 feedback; 2 s startup boost is firmware-timed
FAN-002,PASS,0B 00 00 00 00 00 00 00,expected Byte0=0B; gear2 feedback
FAN-003,PASS,0C 00 00 00 00 00 00 00,expected Byte0=0C; gear3 feedback
FAN-004,PASS,0C 00 00 00 00 00 00 00,expected Byte0=0C; vehicle gear5 clamps to physical3
FAN-005,PASS,09 00 00 00 00 00 00 00,expected Byte0=09; fan switch off
THM-002,PASS,09 00 00 00 00 00 00 00,expected Byte0=09; max fan/heater level configured to2
THM-003,PASS,09 00 00 00 00 00 00 00,expected Byte0=09; valid heater mode; outputs stay off
THM-004,PASS,1B 00 00 00 00 00 00 00,expected Byte0=1B; both requested level3 and clamped to physical2
HEAT-001,PASS,09 00 00 00 00 00 00 00,expected Byte0=09; mode accepted without starting heater
1 TestID Result Actual Notes
2 THM-001 PASS 09 00 00 00 00 00 00 00 expected Byte0=09; fan and heater off
3 FAN-001 PASS 0A 00 00 00 00 00 00 00 expected Byte0=0A; gear1 feedback; 2 s startup boost is firmware-timed
4 FAN-002 PASS 0B 00 00 00 00 00 00 00 expected Byte0=0B; gear2 feedback
5 FAN-003 PASS 0C 00 00 00 00 00 00 00 expected Byte0=0C; gear3 feedback
6 FAN-004 PASS 0C 00 00 00 00 00 00 00 expected Byte0=0C; vehicle gear5 clamps to physical3
7 FAN-005 PASS 09 00 00 00 00 00 00 00 expected Byte0=09; fan switch off
8 THM-002 PASS 09 00 00 00 00 00 00 00 expected Byte0=09; max fan/heater level configured to2
9 THM-003 PASS 09 00 00 00 00 00 00 00 expected Byte0=09; valid heater mode; outputs stay off
10 THM-004 PASS 1B 00 00 00 00 00 00 00 expected Byte0=1B; both requested level3 and clamped to physical2
11 HEAT-001 PASS 09 00 00 00 00 00 00 00 expected Byte0=09; mode accepted without starting heater
@@ -0,0 +1,71 @@
{
"generated_local": "2026-07-30 09:34:50",
"results": [
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09; fan and heater off",
"Result": "PASS",
"TestID": "THM-001"
},
{
"Actual": "0A 00 00 00 00 00 00 00",
"Notes": "expected Byte0=0A; gear1 feedback; 2 s startup boost is firmware-timed",
"Result": "PASS",
"TestID": "FAN-001"
},
{
"Actual": "0B 00 00 00 00 00 00 00",
"Notes": "expected Byte0=0B; gear2 feedback",
"Result": "PASS",
"TestID": "FAN-002"
},
{
"Actual": "0C 00 00 00 00 00 00 00",
"Notes": "expected Byte0=0C; gear3 feedback",
"Result": "PASS",
"TestID": "FAN-003"
},
{
"Actual": "0C 00 00 00 00 00 00 00",
"Notes": "expected Byte0=0C; vehicle gear5 clamps to physical3",
"Result": "PASS",
"TestID": "FAN-004"
},
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09; fan switch off",
"Result": "PASS",
"TestID": "FAN-005"
},
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09; max fan/heater level configured to2",
"Result": "PASS",
"TestID": "THM-002"
},
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09; valid heater mode; outputs stay off",
"Result": "PASS",
"TestID": "THM-003"
},
{
"Actual": "1B 00 00 00 00 00 00 00",
"Notes": "expected Byte0=1B; both requested level3 and clamped to physical2",
"Result": "PASS",
"TestID": "THM-004"
},
{
"Actual": "09 00 00 00 00 00 00 00",
"Notes": "expected Byte0=09; mode accepted without starting heater",
"Result": "PASS",
"TestID": "HEAT-001"
}
],
"scope_note": "CAN control/feedback only; PWM duty needs oscilloscope",
"summary": {
"fail": 0,
"pass": 10,
"total": 10
}
}
@@ -1,12 +1,14 @@
# Seat_Controller_demo
`Seat_Controller_demo` 是 `CYT4BFBCJE` / `CY_MCU_rev_d` / Cortex-M0+ 的单板统一座椅控制器工程。它以已验证的 `Seat_Move_demo` 为安全和四电机骨架,并合入 `Seat_Backrest_Heater_demo` 与 `Seat_Fan_demo` 的 PWM/ADC 驱动和 CAN 控制语义。
`Seat_Controller_demo` 是 `CYT4BFBCJE` / `CY_MCU_rev_d` / Cortex-M0+ Flash 的单板统一座椅控制器工程,集成座椅通风、靠背加热和四电机移动。SDL 公共源码未修改,应用配置和功能代码均位于本工程目录。
## 单板资源
- 共享 CAN4:RX=P3.7、TX=P3.6、收发器/DVDD 使能=P3.1,高电平有效。
- 通风:P11.1 / TCPWM1-60;P11.2 保持 GPIO 低。
- 靠背加热:P16.7 / TCPWM1-65;电流诊断 P12.2 / SAR1 CH6。
- 通风电流:FSDY-AD=P12.6 / SAR1 CH10。
- 四轴电流:M0=P10.6/CH2、M1=P12.1/CH5、M2=P10.7/CH3、M3=P10.5/CH1。六路电流信号统一由 PASS0 SAR1 初始化,避免模块间反初始化。
- 四电机与 Hall:沿用 `Seat_Move_demo` 的 DRV8718、TCPWM1-12/13/14/19/20 和四路 Hall 资源。
- 资源分频:风扇=divider0、SCB4=divider1、电机=divider2、加热=divider3、ADC=divider4;各已分离。
@@ -17,17 +19,40 @@
- RX `0x29E/0x2AA/0x3CE/0x42A`:通风和加热。
- RX `0x300`:四电机命令。
- TX `0x4FA`:byte0 bits0..2 通风状态,bits3..5 加热状态,合成在同一帧中。
- TX `0x301/0x302/0x303`:移动 ACK、状态和诊断。
- TX `0x301/0x302/0x303`:移动 ACK、状态和 Hall 诊断。
- TX `0x304`:四轴电流诊断;B0 MotorID,B1 bits0..4依次为采样有效/零点有效/高电流/低Hall/标定中,B2..3 ADC raw,B4..5停止态零点raw,B6..7该轴堵转电流差值阈值raw。
- TX `0x305`:热管理电流诊断;B0采样有效标志,B1输出状态,B2..3通风电流raw,B4..5靠背加热电流raw,B6/B7为两路ADC累计错误计数(饱和到255)。
CAN 为 500 kbit/s、经典标准帧、11-bit ID、DLC 8。`seat_move_application.c` 只把 `0x300` 交给移动协议,其余已过滤热管理帧交给 `seat_controller_thermal.c`,避免多个模块争抢同一 CAN 外设。
CAN 为 500 kbit/s、经典标准帧、11-bit ID、DLC 8。移动协议版本为 1;支持 `MOVE_ABSOLUTE`、`MOVE_RELATIVE`、`MOVE_TIMED`、`STOP_MOTOR`、`STOP_ALL`、`CALIBRATE_RANGE`、`HOME_KNOWN_RANGE`、`QUERY_STATUS` 和 `CLEAR_FAULT`。四轴定义及完整字节布局见 `Docs/outputs/seat_move_four_motor_20260724/座椅四电机移动功能CAN矩阵表.xlsx`。
2026-07-30 四个独立量程入口已验证 CAN MotorID 与板级物理 Mode 同号映射:M0/physical0 水平前后、M1/physical1 坐盆上下、M2/physical2 靠背前后、M3/physical3 腿托上下。首次测试仍须目视确认没有错误执行机构动作。
## 2026-07-30 实车量程
| 逻辑轴 | 0% 最近端 | 100% 最远端 | MaxHallCount | 物理全量程 | 分辨率 |
|---|---|---|---:|---:|---:|
| M0 水平 | 座椅最前方 | 座椅最后方 | 480 | 215 mm | 0.4479 mm/Hall |
| M1 坐盆 | 坐盆最上方 | 坐盆斜向下方 | 374 | 水平 51 mm、垂直 56 mm | 水平 0.1364、垂直 0.1497 mm/Hall |
| M2 靠背 | 靠背最前方(垂直地面) | 向后躺倒 | 917 | 68° | 0.0742°/Hall |
| M3 腿托 | 腿托最下方(垂直地面) | 向上抬起 | 647 | 72° | 0.1113°/Hall |
`0x302` 报告 0~10000 的绝对归一化位置,`0x303` 报告当前/最大 Hall。物理绝对位置由同一归一化比例换算:M1 同时得到水平与垂直分量。CAN MotorID 0~3 与实测物理 Mode 0~3 同号对应。`PositionValidMask`只表示ECU位置记录可信,不表示座椅位于最近端。首次使用 SMV8 固件或存储无效时不能仅凭默认最大 Hall 推断当前位置;本台架量程已确认,可用 `HOME_KNOWN_RANGE(0x0B)` 将失效轴移动到最近端建立 Hall=0,再执行绝对目标,无需重新测量最远端。如果座椅在ECU掉电时被人工改变或实际位置不可信,即使Mask为0xF也必须真实执行回最近端,不能只修改有效位。
## 安全行为
- 启动最早阶段把电机、P11.1/P11.2 和 P16.7 强制为 GPIO 低。
- 任何热/风输出初始化或运行故障都会关断热/风并锁存全局 driver fault;移动也进入 inhibit。
- 移动保留 `STOP_ALL`、sequence、堵转、端点、超时、Work Flash 和 WDT 机制。
- 四电机使用独立任务、Sequence、Hall、超时和故障上下文;同轴换向先全关并等待死区,不同轴命令不会互相取消。
- 四轴分别具有启动宽限(M0/M1/M2=1 s,M3=2 s)。普通移动仅在“500 ms Hall双边沿窗口不超过2个边沿”且“相对停止态零点的轴电流超过独立阈值并持续200 ms”同时成立时报告 `STALL`;低Hall但电流低或采样过期时不误报堵转,连续5 s无运动后关断并报告 `TIMEOUT`。
- 四轴普通绝对/相对移动默认使用90% PWM占空比;堵转电流差值阈值在重新带载标定前统一使用500 raw保守默认值,优先避免正常运动误报。PWM软件上限保持95%。
- 位置任务因端点/堵转事件停止时,若最终Hall已进入目标±2脉冲范围则按到位完成;底层LastEvent仍由`0x303`保留用于诊断。超过该范围的STALL/TIMEOUT仍按失败处理。
- `HOME_KNOWN_RANGE`/全量标定的寻端语义与普通移动分离。端点要求500 ms内Hall双边沿为0,避免慢速移动仅有下降沿时被误判为端点;已知量程归零若第一次寻近端少于4个真实Hall边沿,会先向远端验证至少4个双边沿再重新寻近端,并校验回程与离端边沿数(允许1边沿误差),禁止把电机未启动直接清零。无法证明往返运动时归零失败并使PositionValid失效。位置和量程仍按Hall上升沿计数。
- `STOP_ALL` 不受旧 Sequence 限制;CAN RX ISR 先立即终止四路 PWM,因此也能打断阻塞式全行程标定,主流程随后完成 ACK 和 Sequence 重同步。
- `CALIBRATE_RANGE` 的 ParamB 是每个寻端行程的实际超时(10~600 s);标定期间继续发送 `0x301/0x302/0x303` 周期反馈。
- `HOME_KNOWN_RANGE(0x0B)` 的 ParamA 必须为 `0x4B52`,ParamB 为最近端寻找超时(10~600 s)。它使用各轴正常90% Duty,只寻找最近端并置 Hall=0/PositionValid=1,保留480/374/917/647,不访问最远端;已经在最近端时会执行约3 Hall的短离端验证后返回。
- 位置记忆使用 SMV8 双槽、版本、序号和 CRC。运动命令启动前先提交较新的 dirty 记录,防止运动中掉电后恢复旧位置;所有电机停稳后保存四轴当前 Hall、最大 Hall、物理绝对量和 PositionValid。正常掉电重启后 `0x302/0x303` 可直接取得上次停止位置。运动中掉电或存储不兼容时位置无效,应用只对失效轴执行 `0x0B` 回最近端,然后继续绝对目标。SMV5/SMV6 的旧量程记录和 SMV7 的旧通道映射记录均不会直接恢复。
- C-SPY `execUserExit()` 同时关断四电机 PWM、风扇 TCPWM60 和加热 TCPWM65,并验证物理输出 latch 为低。
- Motor3 仍未接线、未实标;固件可以解析协议,但上位机默认禁止发送该轴命令。
- 正常车辆上电不要求调试器连接;真正 POR 会清除调试会话标记。若 C-SPY 会话结束或调试器脱离,则本次供电周期锁存安全停机,防止烧录结束后执行器继续动作。
## 构建
@@ -36,4 +61,13 @@ CAN 为 500 kbit/s、经典标准帧、11-bit ID、DLC 8。`seat_move_applicatio
'.\IAR\Seat_Controller_demo.ewp' -build rev_d -log all
```
输出:`IAR/rev_d/Exe/Seat_Controller_demo.hex`。构建不会烧录或启动执行器。首次烧录后的热、风、Motor0..2 组合回归仍必须在台架完成,未完成前不得标记为 HIL verified。
输出:`IAR/rev_d/Exe/Seat_Controller_demo.hex`。量程标定程序位于仓库根目录 `量程测试脚本`。完整台架步骤、测试向量和判定标准见 `Docs/座椅控制器_CAN_测试规范.md`、`Docs/座椅控制器_CAN_测试向量.csv` 与 `Docs/座椅控制器_CAN_测试规范.docx`。
## 当前无示波器 HIL 入口
- `Tests/manual_hil/04_can_full_function.py`:通风、加热和四轴约12%往返冒烟;若位置未知,先只把失效轴回最近端,不重新测量量程。
- `Tests/manual_hil/05_seat_function_bridge.py`:在ZCANPRO内运行的CAN桥接。
- `Tests/manual_hil/run_seat_function_gui.bat`:Windows选择界面,从仓库根目录 `座椅功能模式配置表.xlsx` 连续动态读取当前11种模式/12个场景,显示物理目标、重算Hall、CAN结果并保存人工观察;小憩模式为单场景,并包含新增“小腿解压悬浮模式”。
- `Tests/manual_hil/02_power_memory.py` 默认只做正常掉电恢复;`03_protection_faults.py` 默认只做无需注入设备的协议保护。
本轮不执行 `01_calibration_position.py` 或 `04_waveform_full_function.py`,两者均已设置归档保护。具体顺序和日志回传要求见 `Tests/manual_hil/README_CN.md`。
@@ -36,7 +36,7 @@ CAN4:RX=P3.7,TX=P3.6,经典标准 CAN,500 kbit/s,11-bit ID,DLC=8,I
| `0x302` | TX | 每台电机 100 ms + Event | Byte0 电机 Sub ID,任务/结果、方向、位置、占空比 |
| `0x303` | TX | 每台电机 500 ms + Event | Byte0 电机 Sub ID,底层事件、Hall、校准量程和最大速度 |
`0x300` Byte1:bits0..1 MotorSelector=`0..3`,bits2..3 Direction,bits4..5 ProtocolVersion=`1`,bits6..7=`0`。MotorSelector=`3`在电机类Opcode中表示电机3;在`STOP_ALL`/`SAVE_SHUTDOWN`中由Opcode定义全局语义,仍要求Selector=`3`。除`STOP_ALL`外,16-bit sequence必须按有符号环形比较递增;`STOP_ALL`总是立即生效并重同步sequence。
`0x300` Byte1:bits0..1 MotorSelector=`0..3`,bits2..3 Direction,bits4..5 ProtocolVersion=`1`,bits6..7=`0`。电机类Opcode使用Selector=`0..3`;全局`STOP_ALL`固定要求Selector=`0`。除`STOP_ALL`外,16-bit sequence必须按有符号环形比较递增;`STOP_ALL`总是立即生效并重同步sequence。
`0x301` Byte1 bits0..3为LastOpcode、bits4..7为四路ActiveMask;Byte4 bits0..3为四路RetryMask、bit4为RecordClean、bit5为WatchdogReset、bit6为RecordValid、bit7保留。
@@ -46,7 +46,7 @@ CAN4:RX=P3.7,TX=P3.6,经典标准 CAN,500 kbit/s,11-bit ID,DLC=8,I
| 用途 | ID | Data bytes 0..7 |
| --- | ---: | --- |
| 全停并同步 sequence=1 | `0x300` | `09 13 01 00 00 00 00 00` |
| 全停并同步 sequence=1 | `0x300` | `09 10 01 00 00 00 00 00` |
| 电机0向远端 500 ms/50%,seq=2 | `0x300` | `03 14 02 00 F4 01 32 00` |
| 电机0向近端 500 ms/50%,seq=3 | `0x300` | `03 18 03 00 F4 01 32 00` |
| 电机1向远端 500 ms/50%,seq=4 | `0x300` | `03 15 04 00 F4 01 32 00` |
@@ -56,7 +56,7 @@ CAN4:RX=P3.7,TX=P3.6,经典标准 CAN,500 kbit/s,11-bit ID,DLC=8,I
| 电机3向远端 500 ms/50%,seq=8(接线后) | `0x300` | `03 17 08 00 F4 01 32 00` |
| 电机3向近端 500 ms/50%,seq=9(接线后) | `0x300` | `03 1B 09 00 F4 01 32 00` |
| 停止电机3,seq=10 | `0x300` | `08 13 0A 00 00 00 00 00` |
| 全停,seq=11 | `0x300` | `09 13 0B 00 00 00 00 00` |
| 全停,seq=11 | `0x300` | `09 10 0B 00 00 00 00 00` |
| 保存状态并受控关机,seq=12 | `0x300` | `0C 13 0C 00 00 00 00 00` |
电机3接线确认后,并发错峰测试可在很短间隔内依次发送四帧:
@@ -68,7 +68,7 @@ CAN4:RX=P3.7,TX=P3.6,经典标准 CAN,500 kbit/s,11-bit ID,DLC=8,I
03 17 17 00 E8 03 32 00 motor3 far, 1000 ms, seq=23
```
预期`0x301` ActiveMask逐步变为`1→3→7→15`;四路物理起动相隔约100 ms。测试结束发送`09 13 18 00 00 00 00 00`全停。
预期`0x301` ActiveMask逐步变为`1→3→7→15`;四路物理起动相隔约100 ms。测试结束发送`09 10 18 00 00 00 00 00`全停。
## 4. 位置、校准与存储
@@ -0,0 +1,112 @@
# Seat_Controller_demo 联调测试报告(2026-07-28)
## 1. 测试对象
- 控制器工程:`Seat_Controller_demo`
- 自动标定工程:`Seat_Move_Calibration_demo`
- 目标器件:CYT4BFBCJE / CY_MCU_rev_d / Cortex-M0+ / Flash
- 编译器:IAR EWARM 9.40.1(命令行构建工具 9.2.2.10770)
- CAN 工具:ZCANPRO + USBCANFD-200U 设备0通道0
## 2. 本次结果摘要
| 项目 | 结果 | 实际结果 |
|---|---|---|
| Seat_Controller_demo 全量构建 | PASS | 0 errors,0 warnings |
| Seat_Move_Calibration_demo 全量构建 | PASS | 0 errors,0 warnings |
| IAR 工作区完整性 | PASS | 已补齐并成功打开 `Seat_Controller_demo.eww` |
| 控制器固件下载 | BLOCKED | CMSIS-DAP 报 `Fatal error: Probe not found / Session aborted` |
| Windows 烧录器枚举 | BLOCKED | 未发现 CMSIS-DAP、J-Link、KitProg、MiniProg、Infineon/Cypress/SEGGER 调试设备 |
| USBCANFD-200U 枚举 | PASS | Windows 正常识别 `USBCANFD-200U`,VID_3068/PID_0009 |
| CAN 物理发送 | BLOCKED | ZCANPRO 报“发送失败”,无总线 ACK,错误帧计数由0增至17 |
| 控制器 CAN 功能用例 | NOT RUN | 新镜像未烧录且总线物理发送失败,不得引用历史帧判定 |
| 四轴自动标定 | NOT RUN | 新镜像未烧录;不得在当前链路状态下启动机械全行程 |
## 3. 构建证据
### 3.1 Seat_Controller_demo
命令:
```text
IarBuild.exe Seat_Controller_demo\IAR\Seat_Controller_demo.ewp -build rev_d -log all
```
结果:
```text
Total number of errors: 0
Total number of warnings: 0
Build succeeded
```
HEX SHA-256:
```text
9406655A88F19B98B66DFE825247AC9807F5F9C9AE463D6C0D5C6A328E9EF8C2
```
### 3.2 Seat_Move_Calibration_demo
命令:
```text
IarBuild.exe Seat_Move_Calibration_demo\IAR\Seat_Move_Calibration_demo.ewp -build rev_d -log errors
```
结果:
```text
Total number of errors: 0
Total number of warnings: 0
Build succeeded
```
HEX SHA-256:
```text
888D0A2A10E2D67BB3349ACBD609121E938257FF432899DDF041ABA206D19741
```
### 3.3 CAN 矩阵与测试集
- `座椅移动功能CAN矩阵表.xlsx` SHA-256:`D72D88D21CA92E4346C5CACDFFC246A58005C3660B7E3FC72BEE42C10BCB0E5E`
- `座椅控制器_CAN_测试向量.csv`:63条、16列、TestID 63个且无重复。
## 4. IAR 下载实测
1. 在 IAR 中通过 `File > Open Workspace` 打开 `Seat_Controller_demo.eww`。
2. 执行 Make,IDE 显示 `Build succeeded`、Errors 0、Warnings 0。
3. 执行 Download and Debug。
4. CMSIS-DAP 返回:
```text
Fatal error: Probe not found
Session aborted!
```
5. Windows PnP 复查没有任何在场的 CMSIS-DAP/J-Link/KitProg/MiniProg/Infineon/Cypress/SEGGER 调试器。工程调试配置仍为参考工程一致的 `CMSISDAP_ID` 和 Infineon `CYT4.ProbeConfig`,因此当前失败发生在探针枚举阶段,不是编译、链接、器件宏或应用代码阶段。
## 5. CAN 物理链路实测
1. Windows 正常枚举 USBCANFD-200U。
2. ZCANPRO 中设备0通道0处于已启动状态;历史视图中保留了此前的 `0x300`/`0x301` 各6帧,这些帧不是本次新镜像的验证结果。
3. 使用窗口内原有模板尝试发送标准 Classic CAN、DLC=8 的 `0x300 00 03 00 00 00 00 00 00`。
4. ZCANPRO 返回“发送失败”;发送/接收计数仍为6/6,错误帧计数从0升至17。
5. 该结果表明当前 CAN 总线上没有形成有效 ACK,常见原因包括开发板未供电、收发器未使能、CANH/CANL/地线连接错误、终端电阻缺失、波特率不一致或通道接错。由于报文未成功发送,本次没有产生受控执行器动作。
## 6. 恢复联调前检查表
按顺序完成以下检查后再继续:
1. 确认开发板12 V及逻辑电源均正常,调试器电源/状态灯正常。
2. 重新插拔烧录器 USB 数据线,并在 Windows 设备管理器中确认出现 CMSIS-DAP 类设备;只看到 USBCANFD-200U 不算烧录器在线。
3. 确认 SWDIO、SWCLK、XRES、GND、VTREF 接线,VTREF 电压与目标板一致。
4. 确认 CANH、CANL、CAN_GND 未反接,台架两端总终端约60 Ω,并确认使用设备0通道0。
5. 确认 ZCANPRO 通道为 500 kbit/s、Classic CAN、11-bit 标准帧。
6. 探针恢复后先烧录 `Seat_Controller_demo.hex`,复位并确认无动作上电;然后依次执行 SYS-002、MOV-001 STOP_ALL、MOV-002 QUERY 和热管理静态用例。
7. 只有在四轴映射、方向、急停和机械空间人工确认后,才允许执行短时点动、多轴并发和自动全行程标定。
## 7. 本次结论
软件构建、工程结构和测试资产通过离线验证;实机烧录与 CAN 功能测试因烧录器未被 Windows 枚举、CAN 总线无 ACK 而阻塞。恢复硬件链路后必须从安全上电和 STOP_ALL 用例重新开始,不能将 ZCANPRO 历史帧或旧固件响应记为本次 PASS。
@@ -0,0 +1,132 @@
from __future__ import annotations
import csv
from pathlib import Path
from docx import Document
from docx.oxml.ns import qn
HERE = Path(__file__).resolve().parent
DOCX = HERE / "座椅控制器_CAN_测试规范.docx"
CSV = HERE / "座椅控制器_CAN_测试向量.csv"
CONTENT_WIDTH_DXA = 9360
def attr_int(node, name):
return int(node.get(qn(name)))
def main():
doc = Document(DOCX)
errors = []
section = doc.sections[0]
expected_section = {
"page_width": 12240,
"page_height": 15840,
"top_margin": 1440,
"right_margin": 1440,
"bottom_margin": 1440,
"left_margin": 1440,
"header_distance": 708,
"footer_distance": 708,
}
for key, expected in expected_section.items():
actual = int(getattr(section, key).twips)
if actual != expected:
errors.append(f"section {key}: {actual} != {expected}")
expected_styles = {
"Normal": (11, 0, 6, 1.25),
"Heading 1": (16, 18, 10, None),
"Heading 2": (13, 14, 7, None),
"Heading 3": (12, 10, 5, None),
}
for name, (size, before, after, line) in expected_styles.items():
style = doc.styles[name]
if round(style.font.size.pt, 2) != size:
errors.append(f"style {name} size {style.font.size.pt} != {size}")
pf = style.paragraph_format
actual_before = pf.space_before.pt if pf.space_before is not None else 0
actual_after = pf.space_after.pt if pf.space_after is not None else 0
if round(actual_before, 2) != before:
errors.append(f"style {name} before mismatch")
if round(actual_after, 2) != after:
errors.append(f"style {name} after mismatch")
if line is not None and pf.line_spacing != line:
errors.append(f"style {name} line spacing {pf.line_spacing} != {line}")
for table_index, table in enumerate(doc.tables):
tbl_pr = table._tbl.tblPr
tbl_w = tbl_pr.find(qn("w:tblW"))
tbl_ind = tbl_pr.find(qn("w:tblInd"))
layout = tbl_pr.find(qn("w:tblLayout"))
if tbl_w is None or attr_int(tbl_w, "w:w") != CONTENT_WIDTH_DXA:
errors.append(f"table {table_index} tblW invalid")
if tbl_ind is None or attr_int(tbl_ind, "w:w") != 120:
errors.append(f"table {table_index} tblInd invalid")
if layout is None or layout.get(qn("w:type")) != "fixed":
errors.append(f"table {table_index} layout is not fixed")
grid = [attr_int(col, "w:w") for col in table._tbl.tblGrid]
if sum(grid) != CONTENT_WIDTH_DXA:
errors.append(f"table {table_index} grid sum {sum(grid)}")
for row_index, row in enumerate(table.rows):
if len(row.cells) != len(grid):
errors.append(f"table {table_index} row {row_index} cell count")
continue
for col_index, cell in enumerate(row.cells):
tc_pr = cell._tc.get_or_add_tcPr()
tc_w = tc_pr.find(qn("w:tcW"))
if tc_w is None or attr_int(tc_w, "w:w") != grid[col_index]:
errors.append(
f"table {table_index} row {row_index} col {col_index} tcW"
)
tc_mar = tc_pr.find(qn("w:tcMar"))
if tc_mar is None:
errors.append(
f"table {table_index} row {row_index} col {col_index} margins"
)
continue
expected_margins = {"top": 80, "bottom": 80, "start": 120, "end": 120}
for tag, expected in expected_margins.items():
node = tc_mar.find(qn(f"w:{tag}"))
if node is None or attr_int(node, "w:w") != expected:
errors.append(
f"table {table_index} row {row_index} col {col_index} {tag}"
)
all_text = "\n".join(p.text for p in doc.paragraphs)
for table in doc.tables:
for row in table.rows:
all_text += "\n" + " | ".join(cell.text for cell in row.cells)
required = [
"SMV8", "MaxHall", "480", "374", "917", "647",
"215 mm", "水平51 mm", "垂直56 mm", "68°", "72°",
"0x29E", "0x4FA", "0x300", "0x301", "0x302", "0x303",
"HOME_KNOWN_RANGE", "0x4B52", "HOM-004",
"运动中掉电", "PositionValid=0",
]
for item in required:
if item not in all_text:
errors.append(f"missing required text: {item}")
if "�" in all_text or "鏄" in all_text or "搴ф" in all_text:
errors.append("mojibake detected")
with CSV.open("r", encoding="utf-8-sig", newline="") as f:
rows = list(csv.DictReader(f))
for row in rows:
if None in row or any(value is None for value in row.values()):
errors.append(f"malformed CSV row: {row.get('TestID', '<unknown>')}")
if row["TestID"] not in all_text:
errors.append(f"test vector missing from DOCX: {row['TestID']}")
print(f"paragraphs={len(doc.paragraphs)} tables={len(doc.tables)} tests={len(rows)}")
if errors:
for error in errors:
print(f"ERROR: {error}")
raise SystemExit(1)
print("STRUCTURAL_AUDIT=PASS")
if __name__ == "__main__":
main()
@@ -0,0 +1,780 @@
from __future__ import annotations
import csv
from collections import OrderedDict
from pathlib import Path
from docx import Document
from docx.enum.section import WD_SECTION_START
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
HERE = Path(__file__).resolve().parent
CSV_PATH = HERE / "座椅控制器_CAN_测试向量.csv"
OUTPUT_PATH = HERE / "座椅控制器_CAN_测试规范.docx"
CONTENT_WIDTH_DXA = 9360
TABLE_INDENT_DXA = 120
CELL_TOP_BOTTOM_DXA = 80
CELL_START_END_DXA = 120
BLUE = "2E74B5"
DARK_BLUE = "1F4D78"
INK = "0B2545"
LIGHT_BLUE = "E8EEF5"
LIGHT_GRAY = "F2F4F7"
PALE_BLUE = "F4F6F9"
WHITE = "FFFFFF"
GRAY = "666666"
RED = "9B1C1C"
def set_run_font(run, size=None, bold=None, color=None, italic=None,
latin="Calibri", east_asia="Microsoft YaHei"):
run.font.name = latin
rpr = run._element.get_or_add_rPr()
rfonts = rpr.find(qn("w:rFonts"))
if rfonts is None:
rfonts = OxmlElement("w:rFonts")
rpr.insert(0, rfonts)
rfonts.set(qn("w:ascii"), latin)
rfonts.set(qn("w:hAnsi"), latin)
rfonts.set(qn("w:eastAsia"), east_asia)
if size is not None:
run.font.size = Pt(size)
if bold is not None:
run.bold = bold
if italic is not None:
run.italic = italic
if color is not None:
run.font.color.rgb = RGBColor.from_string(color)
def set_style_font(style, size, color="000000", bold=False):
style.font.name = "Calibri"
style.font.size = Pt(size)
style.font.bold = bold
style.font.color.rgb = RGBColor.from_string(color)
rpr = style.element.get_or_add_rPr()
rfonts = rpr.find(qn("w:rFonts"))
if rfonts is None:
rfonts = OxmlElement("w:rFonts")
rpr.insert(0, rfonts)
rfonts.set(qn("w:ascii"), "Calibri")
rfonts.set(qn("w:hAnsi"), "Calibri")
rfonts.set(qn("w:eastAsia"), "Microsoft YaHei")
def set_cell_margins(cell, top=CELL_TOP_BOTTOM_DXA,
start=CELL_START_END_DXA,
bottom=CELL_TOP_BOTTOM_DXA,
end=CELL_START_END_DXA):
tc_pr = cell._tc.get_or_add_tcPr()
tc_mar = tc_pr.first_child_found_in("w:tcMar")
if tc_mar is None:
tc_mar = OxmlElement("w:tcMar")
tc_pr.append(tc_mar)
for tag, value in (("top", top), ("start", start),
("bottom", bottom), ("end", end)):
node = tc_mar.find(qn(f"w:{tag}"))
if node is None:
node = OxmlElement(f"w:{tag}")
tc_mar.append(node)
node.set(qn("w:w"), str(value))
node.set(qn("w:type"), "dxa")
def set_cell_fill(cell, fill):
tc_pr = cell._tc.get_or_add_tcPr()
shd = tc_pr.find(qn("w:shd"))
if shd is None:
shd = OxmlElement("w:shd")
tc_pr.append(shd)
shd.set(qn("w:fill"), fill)
shd.set(qn("w:val"), "clear")
def set_repeat_table_header(row):
tr_pr = row._tr.get_or_add_trPr()
tbl_header = tr_pr.find(qn("w:tblHeader"))
if tbl_header is None:
tbl_header = OxmlElement("w:tblHeader")
tr_pr.append(tbl_header)
tbl_header.set(qn("w:val"), "true")
def set_table_borders(table, color="B7C9DB", size="6"):
tbl_pr = table._tbl.tblPr
borders = tbl_pr.find(qn("w:tblBorders"))
if borders is None:
borders = OxmlElement("w:tblBorders")
tbl_pr.append(borders)
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
node = borders.find(qn(f"w:{edge}"))
if node is None:
node = OxmlElement(f"w:{edge}")
borders.append(node)
node.set(qn("w:val"), "single")
node.set(qn("w:sz"), size)
node.set(qn("w:space"), "0")
node.set(qn("w:color"), color)
def set_table_geometry(table, widths_dxa, indent_dxa=TABLE_INDENT_DXA):
if sum(widths_dxa) != CONTENT_WIDTH_DXA:
raise ValueError(f"table width must be {CONTENT_WIDTH_DXA}: {widths_dxa}")
table.autofit = False
tbl = table._tbl
tbl_pr = tbl.tblPr
tbl_w = tbl_pr.find(qn("w:tblW"))
if tbl_w is None:
tbl_w = OxmlElement("w:tblW")
tbl_pr.append(tbl_w)
tbl_w.set(qn("w:w"), str(CONTENT_WIDTH_DXA))
tbl_w.set(qn("w:type"), "dxa")
tbl_ind = tbl_pr.find(qn("w:tblInd"))
if tbl_ind is None:
tbl_ind = OxmlElement("w:tblInd")
tbl_pr.append(tbl_ind)
tbl_ind.set(qn("w:w"), str(indent_dxa))
tbl_ind.set(qn("w:type"), "dxa")
layout = tbl_pr.find(qn("w:tblLayout"))
if layout is None:
layout = OxmlElement("w:tblLayout")
tbl_pr.append(layout)
layout.set(qn("w:type"), "fixed")
grid = tbl.tblGrid
for child in list(grid):
grid.remove(child)
for width in widths_dxa:
col = OxmlElement("w:gridCol")
col.set(qn("w:w"), str(width))
grid.append(col)
for row in table.rows:
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
for index, cell in enumerate(row.cells):
tc_pr = cell._tc.get_or_add_tcPr()
tc_w = tc_pr.find(qn("w:tcW"))
if tc_w is None:
tc_w = OxmlElement("w:tcW")
tc_pr.append(tc_w)
tc_w.set(qn("w:w"), str(widths_dxa[index]))
tc_w.set(qn("w:type"), "dxa")
cell.width = Inches(widths_dxa[index] / 1440)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
set_cell_margins(cell)
set_table_borders(table)
def set_cell_text(cell, text, bold=False, color="000000", size=9,
align=WD_ALIGN_PARAGRAPH.LEFT):
cell.text = ""
p = cell.paragraphs[0]
p.alignment = align
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
p.paragraph_format.line_spacing = 1.08
r = p.add_run(str(text))
set_run_font(r, size=size, bold=bold, color=color)
def add_table(doc, headers, rows, widths_dxa, font_size=9,
center_cols=(), header_fill=LIGHT_BLUE):
table = doc.add_table(rows=1, cols=len(headers))
table.style = "Table Grid"
hdr = table.rows[0]
set_repeat_table_header(hdr)
for i, header in enumerate(headers):
set_cell_fill(hdr.cells[i], header_fill)
set_cell_text(hdr.cells[i], header, bold=True, color=INK, size=font_size,
align=WD_ALIGN_PARAGRAPH.CENTER)
for row_index, values in enumerate(rows):
cells = table.add_row().cells
for i, value in enumerate(values):
if row_index % 2 == 1:
set_cell_fill(cells[i], "F8FAFC")
set_cell_text(
cells[i], value, size=font_size,
align=(WD_ALIGN_PARAGRAPH.CENTER if i in center_cols
else WD_ALIGN_PARAGRAPH.LEFT),
)
set_table_geometry(table, widths_dxa)
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(2)
return table
def add_body(doc, text, bold_prefix=None):
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(6)
p.paragraph_format.line_spacing = 1.25
if bold_prefix and text.startswith(bold_prefix):
r1 = p.add_run(bold_prefix)
set_run_font(r1, bold=True)
r2 = p.add_run(text[len(bold_prefix):])
set_run_font(r2)
else:
r = p.add_run(text)
set_run_font(r)
return p
def add_bullet(doc, text, level=0):
p = doc.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2")
p.paragraph_format.left_indent = Inches(0.375 + level * 0.25)
p.paragraph_format.first_line_indent = Inches(-0.188)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(4)
p.paragraph_format.line_spacing = 1.25
r = p.add_run(text)
set_run_font(r)
return p
def add_number(doc, text):
p = doc.add_paragraph(style="List Number")
p.paragraph_format.left_indent = Inches(0.375)
p.paragraph_format.first_line_indent = Inches(-0.188)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(4)
p.paragraph_format.line_spacing = 1.25
r = p.add_run(text)
set_run_font(r)
return p
def add_callout(doc, label, text, fill=PALE_BLUE, color=INK):
table = doc.add_table(rows=1, cols=1)
cell = table.cell(0, 0)
set_cell_fill(cell, fill)
cell.text = ""
p = cell.paragraphs[0]
p.paragraph_format.space_before = Pt(2)
p.paragraph_format.space_after = Pt(2)
p.paragraph_format.line_spacing = 1.15
r = p.add_run(f"{label} ")
set_run_font(r, bold=True, color=color)
r = p.add_run(text)
set_run_font(r, color=color)
set_table_geometry(table, [CONTENT_WIDTH_DXA])
set_table_borders(table, color="C6D7E8", size="8")
p2 = doc.add_paragraph()
p2.paragraph_format.space_after = Pt(2)
def add_field(paragraph, instr):
run = paragraph.add_run()
fld_char = OxmlElement("w:fldChar")
fld_char.set(qn("w:fldCharType"), "begin")
instr_text = OxmlElement("w:instrText")
instr_text.set(qn("xml:space"), "preserve")
instr_text.text = instr
fld_sep = OxmlElement("w:fldChar")
fld_sep.set(qn("w:fldCharType"), "separate")
text = OxmlElement("w:t")
text.text = "1"
fld_end = OxmlElement("w:fldChar")
fld_end.set(qn("w:fldCharType"), "end")
run._r.extend([fld_char, instr_text, fld_sep, text, fld_end])
set_run_font(run, size=9, color=GRAY)
def configure_document(doc):
section = doc.sections[0]
section.page_width = Inches(8.5)
section.page_height = Inches(11)
section.top_margin = Inches(1.0)
section.right_margin = Inches(1.0)
section.bottom_margin = Inches(1.0)
section.left_margin = Inches(1.0)
section.header_distance = Inches(0.492)
section.footer_distance = Inches(0.492)
normal = doc.styles["Normal"]
set_style_font(normal, 11)
normal.paragraph_format.space_before = Pt(0)
normal.paragraph_format.space_after = Pt(6)
normal.paragraph_format.line_spacing = 1.25
for name, size, color, before, after in (
("Heading 1", 16, BLUE, 18, 10),
("Heading 2", 13, BLUE, 14, 7),
("Heading 3", 12, DARK_BLUE, 10, 5),
):
style = doc.styles[name]
set_style_font(style, size, color=color, bold=True)
style.paragraph_format.space_before = Pt(before)
style.paragraph_format.space_after = Pt(after)
style.paragraph_format.keep_with_next = True
for name in ("List Bullet", "List Bullet 2", "List Number"):
style = doc.styles[name]
set_style_font(style, 11)
style.paragraph_format.space_after = Pt(4)
style.paragraph_format.line_spacing = 1.25
header = section.header
p = header.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
p.paragraph_format.space_after = Pt(0)
r = p.add_run("Seat Controller | CAN/HIL Test Specification")
set_run_font(r, size=9, bold=True, color=GRAY)
footer = section.footer
p = footer.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
p.paragraph_format.space_after = Pt(0)
r = p.add_run("V2.3 | 2026-07-30 | Page ")
set_run_font(r, size=9, color=GRAY)
add_field(p, "PAGE")
r = p.add_run(" / ")
set_run_font(r, size=9, color=GRAY)
add_field(p, "NUMPAGES")
settings = doc.settings.element
update_fields = settings.find(qn("w:updateFields"))
if update_fields is None:
update_fields = OxmlElement("w:updateFields")
settings.append(update_fields)
update_fields.set(qn("w:val"), "true")
def add_cover(doc):
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(70)
p.paragraph_format.space_after = Pt(14)
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = p.add_run("工程测试规范")
set_run_font(r, size=12, bold=True, color=BLUE)
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.paragraph_format.space_after = Pt(10)
r = p.add_run("Seat_Controller_demo")
set_run_font(r, size=30, bold=True, color=INK)
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.paragraph_format.space_after = Pt(6)
r = p.add_run("座椅通风、加热与四轴移动 CAN/HIL 测试规范")
set_run_font(r, size=18, bold=True, color=DARK_BLUE)
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.paragraph_format.space_after = Pt(42)
r = p.add_run("含四轴确认量程、SMV8位置记忆与未知位置单端恢复")
set_run_font(r, size=11, color=GRAY)
add_table(
doc,
["文档项", "内容"],
[
["版本", "V2.3 / 2026-07-30"],
["目标硬件", "CYT4BFBCJE / CY_MCU_rev_d / Cortex-M0+ Flash"],
["CAN", "Classic CAN,11-bit,500 kbit/s,DLC=8,Intel/Little-endian"],
["软件工程", "Seat_Controller_demo"],
["测试范围", "通风、靠背加热、四轴移动、标定、反馈、安全停机、掉电记忆"],
],
[1800, 7560],
font_size=10,
center_cols=(0,),
)
add_callout(
doc,
"安全等级 P0",
"任何全行程、堵转、加热或运动中断电测试都必须在固定台架、限流电源、机械净空和硬件急停条件下执行。",
fill="FFF4E5",
color=RED,
)
doc.add_page_break()
def add_overview(doc):
doc.add_heading("1 目的、范围与交付判定", level=1)
add_body(doc, "本规范用于验证 Seat_Controller_demo 在同一 CAN4 节点上融合座椅通风、靠背加热和四路电机移动后的功能、时序、故障安全与绝对位置连续性。测试结果分为 PASS、FAIL、BLOCKED、NOT RUN;硬件条件不足不能以 PASS 代替。")
add_bullet(doc, "静态范围:IAR 全量构建、目标宏、资源冲突、输出初始低电平。")
add_bullet(doc, "CAN 范围:热管理报文 0x29E/0x2AA/0x3CE/0x42A/0x4FA;移动报文 0x300~0x303。")
add_bullet(doc, "动作范围:单轴点动、绝对/相对位置、多轴并发、标定、停止与故障注入。")
add_bullet(doc, "记忆范围:SMV8 dirty/clean 双槽、CRC、正常掉电恢复、运动中掉电失效;SMV5/SMV6 旧量程及 SMV7 旧通道映射记录不恢复。")
add_callout(doc, "构建基线", "2026-07-30 使用 IAR EWARM 9.2 rev_d 配置全量构建;四轴量程入口均改为机械端点标定,台架动作与掉电用例由手动 HIL 脚本留存日志。")
doc.add_heading("1.1 通过、失败与阻塞", level=2)
add_table(
doc,
["结果", "定义", "处理"],
[
["PASS", "CAN、物理动作、时序与安全判据全部满足", "保留原始报文、波形和测量记录"],
["FAIL", "存在误动作、方向错误、停机超时、字段错误、丢帧或存储错误", "立即全停,记录复现条件并进入缺陷流程"],
["BLOCKED", "机械、仪器、线束或安全条件不满足", "明确阻塞项,不得按通过处理"],
["NOT RUN", "尚未执行", "列入后续计划"],
],
[950, 4610, 3800],
font_size=9,
center_cols=(0,),
)
def add_calibration(doc):
doc.add_heading("2 四轴坐标、量程与换算", level=1)
add_body(doc, "统一坐标定义:最近端=0 Hall=0%=绝对位置0;TowardFar 从最近端向最远端运动且 Hall 递增;TowardNear 反向且 Hall 递减;最远端=MaxHallCount=100%。")
add_table(
doc,
["ID", "功能/通道", "0% 最近端", "100% 最远端", "MaxHall", "全量程/分辨率"],
[
["M0", "水平前后\nphysical0", "座椅最前方", "座椅最后方", "480", "215 mm\n0.4479 mm/Hall"],
["M1", "坐盆斜上下\nphysical1", "坐盆最上方", "坐盆斜向下方", "374", "水平51 mm / 0.1364\n垂直56 mm / 0.1497"],
["M2", "靠背前后\nphysical2", "靠背最前方\n垂直地面", "向后躺倒", "917", "68°\n0.0742°/Hall"],
["M3", "腿托上下\nphysical3", "腿托最下方\n垂直地面", "向上抬起", "647", "72°\n0.1113°/Hall"],
],
[650, 1450, 1750, 1750, 1050, 2710],
font_size=8.5,
center_cols=(0, 4),
)
add_body(doc, "换算公式:AbsoluteValue = round(HallCount × FullScale / MaxHallCount);Normalized = round(HallCount × 10000 / MaxHallCount)。M1 的水平与垂直分量共用同一归一化比例。")
doc.add_heading("2.1 典型绝对位置检查点", level=2)
add_table(
doc,
["轴", "0%", "25%", "50%", "75%", "100%"],
[
["M0 Hall / mm", "0 / 0", "120 / 53.75", "240 / 107.50", "360 / 161.25", "480 / 215"],
["M1 Hall / H,V mm", "0 / 0,0", "94 / 12.75,14", "187 / 25.5,28", "281 / 38.25,42", "374 / 51,56"],
["M2 Hall / deg", "0 / 0", "229 / 17", "459 / 34", "688 / 51", "917 / 68"],
["M3 Hall / deg", "0 / 0", "162 / 18", "324 / 36", "485 / 54", "647 / 72"],
],
[1600, 1450, 1578, 1578, 1578, 1576],
font_size=8.5,
center_cols=(0, 1, 2, 3, 4, 5),
)
add_callout(doc, "首次上电/位置未知", "默认 MaxHall 只提供量程基线,不能提供当前原点。PositionValid只表示ECU位置记录可信,不表示机构位于最近端。本台架量程已确认;SMV8 无有效 clean 记录时,对失效轴执行 HOME_KNOWN_RANGE(0x0B)。若机构在ECU掉电时被人工改变或实际位置不可信,即使Mask为0xF也必须真实回最近端建立Hall=0,不能只修改有效位。保留480/374/917/647,无需访问最远端。")
def add_can_protocol(doc):
doc.add_heading("3 CAN 接口规范", level=1)
add_table(
doc,
["方向", "ID", "周期/触发", "用途"],
[
["RX", "0x29E", "事件", "风扇/加热主控制与档位"],
["RX", "0x2AA", "事件", "风扇/加热最大配置"],
["RX", "0x3CE", "事件", "加热模式有效性"],
["RX", "0x42A", "事件", "风扇/加热开关"],
["TX", "0x4FA", "100 ms + 状态变化", "风扇/加热组合反馈"],
["RX", "0x300", "事件", "四轴移动命令"],
["TX", "0x301", "100 ms + 状态变化", "系统状态与ACK"],
["TX", "0x302", "每轴100 ms;轴间约25 ms", "任务、归一化位置、驱动等级"],
["TX", "0x303", "每轴500 ms;轴间约125 ms", "Hall、量程、速度、诊断"],
],
[900, 950, 2850, 4660],
font_size=9,
center_cols=(0, 1),
)
doc.add_heading("3.1 热管理 RX 与 TX", level=2)
add_table(
doc,
["ID", "字节/位", "定义", "有效值与动作"],
[
["0x29E", "B0[1:0]", "FanOn", "1=开,2=关"],
["0x29E", "B0[3:2]", "FanMode", "1或2有效"],
["0x29E", "B0[5:4]", "HeaterOn", "1=开,2=关"],
["0x29E", "B2[3:0]", "FanGear", "1~5,物理夹紧至1~3及配置上限"],
["0x29E", "B2[7:4]", "HeaterGear", "1~5,物理夹紧至1~3及配置上限"],
["0x2AA", "B0", "SubId", "必须为1"],
["0x2AA", "B2[2:0]", "FanMax", "1→1档,2→2档,其余有效值→3档"],
["0x2AA", "B2[5:3]", "HeaterMax", "1→1档,2→2档,其余有效值→3档"],
["0x3CE", "B0[5:4]", "HeaterMode", "1或2有效;其它值使加热安全关闭"],
["0x42A", "B0[1:0]/[3:2]", "Fan/Heater switch", "1=开,2=关"],
["0x4FA", "B0[2:0]/[5:3]", "Fan/Heater feedback", "0=故障/无效,1=关,2/3/4=1/2/3档"],
],
[1000, 1500, 2200, 4660],
font_size=8.5,
center_cols=(0, 1),
)
add_body(doc, "风扇 PWM 为100 Hz:首次启动100%持续2.00 s,之后1/2/3档为50%/70%/100%;运行中换档不重复启动增压。靠背加热 PWM 为100 Hz,1/2/3档为30%/60%/90%,连续运行600 s后安全关闭。")
doc.add_heading("3.2 移动命令 0x300", level=2)
add_table(
doc,
["Byte", "字段", "说明"],
[
["0", "Opcode", "01绝对、02相对、03定时、08单停、09全停、0A标定、0B已知量程回最近端、0C查询、0D清故障"],
["1", "Control", "0x10 | MotorID[1:0] | Direction[3:2];Direction 0/1/2=停止/Far/Near"],
["2..3", "Sequence", "uint16 little-endian;重复帧不重做,旧序列拒绝;STOP_ALL例外"],
["4..5", "ParamA", "命令参数,little-endian"],
["6..7", "ParamB", "命令参数,little-endian"],
],
[950, 1800, 6610],
font_size=9,
center_cols=(0,),
)
add_table(
doc,
["Opcode", "命令", "ParamA", "ParamB"],
[
["01", "MOVE_ABSOLUTE", "0~10000", "0=默认或100~60000 ms"],
["02", "MOVE_RELATIVE", "int16 -10000~10000", "0=默认或100~60000 ms"],
["03", "MOVE_TIMED", "100~60000 ms", "驱动等级×10,300~1000"],
["08", "STOP_MOTOR", "0", "0"],
["09", "STOP_ALL", "0", "0"],
["0A", "CALIBRATE_RANGE", "0xA55A", "每寻端10~600 s"],
["0B", "HOME_KNOWN_RANGE", "0x4B52", "最近端寻找10~600 s"],
["0C", "QUERY_STATUS", "0", "0"],
["0D", "CLEAR_FAULT", "0xC33C", "0"],
],
[950, 2500, 2950, 2960],
font_size=9,
center_cols=(0,),
)
doc.add_heading("3.3 移动反馈 0x301~0x303", level=2)
add_table(
doc,
["ID", "字节", "内容"],
[
["0x301", "B0", "低4位ACK;高4位SystemState"],
["0x301", "B1/B2..3/B4", "RejectReason / Sequence / LastOpcode"],
["0x301", "B5", "bit0..3 ActiveMask,bit4 inhibit,bit6 calibration,bit7 fault summary"],
["0x301", "B6/B7", "低4位FaultedMask,高4位PositionValidMask / Alive+固件修订号"],
["0x302", "B0/B1", "MotorID / 低4位TaskState,高4位Result"],
["0x302", "B2", "方向、active、position valid、near、far、stall、driver fault"],
["0x302", "B3..4/B5..6/B7", "Sequence / 0..10000归一化位置(无效=FFFF)/ duty"],
["0x303", "B0/B1", "MotorID / 低4位LastEvent,高4位FaultFlags"],
["0x303", "B2..3/B4..5", "HallCount / MaxHallCount"],
["0x303", "B6/B7", "Hall速度0.5 pulse/s/LSB / Retry+Alive"],
],
[1050, 2100, 6210],
font_size=8.5,
center_cols=(0, 1),
)
add_body(doc, "ACK:0 NONE、1 ACCEPTED、2 REJECTED、3 DUPLICATE、4 STOP_ALL_ACCEPTED。SystemState:0 INIT、1 READY、2 ACTIVE、3 INHIBITED、4 FAULT、5 CALIBRATION。")
add_body(doc, "RejectReason:0 NONE、1 BAD_DLC、2 BAD_VERSION、3 BAD_OPCODE、4 BAD_MOTOR、5 BAD_DIRECTION、6 PARAM_RANGE、7 POSITION_INVALID、8 SEQUENCE_OLD、9 BUSY_SAME_MOTOR、10 INTERLOCK、11 FAULT_ACTIVE、12 SERVICE_MODE_REQUIRED、13 CAL_DATA_INVALID、14 INTERNAL_ERROR。")
add_body(doc, "TaskState:0 IDLE、1 ACCEPTED、2 POSITION_RUN、3 TIMED_RUN、4 CALIBRATION、5 STOPPING、6 COMPLETE、7 FAULT。Result:0 NONE、1 RUNNING、2 OK、3 STOPPED_BY_CMD、4 ENDPOINT_REACHED、5 STALL、6 TIMEOUT、7 DRIVER_FAULT、8 HALL_FAULT、9 INVALID_CMD、10 INTERLOCKED、11 CALIBRATION_FAILED。")
def add_persistence(doc):
doc.add_heading("4 SMV8 本地绝对位置记忆", level=1)
add_body(doc, "SMV8 使用两个连续128-byte Work Flash 小扇区交替提交记录。每轴保存绝对 Hall、MaxHall、最大速度、累计 Hall 上升沿、物理绝对量与 PositionValid;记录还包含 Magic/Version、单调Sequence、Clean标志和CRC32。SMV5/SMV6 使用旧量程尺度,SMV7 使用旧非同号通道映射;固件升级后均视为不兼容记录,不直接恢复旧绝对位置。")
add_number(doc, "动作命令启动前写入较新 dirty 记录,写完后才允许 PWM 启动。")
add_number(doc, "绝对、相对、定时、全量程标定或已知量程回最近端动作执行;多轴可并发,记录保持 dirty。")
add_number(doc, "应用队列为空、所有任务结束且底层惯性计数停止后,采集四轴最终状态。")
add_number(doc, "向另一槽写入较新 clean 记录并逐字回读校验;相同 clean 数据不重复擦写。")
add_number(doc, "下次上电选取序列较新的CRC有效记录;只有 clean 记录才恢复绝对位置。")
add_callout(doc, "掉电安全与恢复", "运动中掉电时,较新的 dirty 记录会压过运动前 clean 记录,因此旧位置不会被误恢复为有效。重启后 PositionValid=0,绝对/相对命令先被拒绝;应用仅对失效轴执行0x0B回最近端,确认Hall=0且MaxHall未变后再继续目标。", fill="FFF4E5", color=RED)
add_callout(doc, "PositionValid与机械归零", "PositionValidMask=0xF且四轴处于非零位置是正常状态,表示SMV8恢复了当前绝对位置;只有Near=1且Hall约为0才证明对应轴在最近端。位置可信性未知时,测试界面必须强制四轴逐一真实执行0x0B。归零使用90% Duty;端点要求500 ms Hall双边沿为0。第一次寻近端少于4个真实Hall边沿时,固件先向远端验证至少4个双边沿再重新寻近端,并校验回程与离端边沿数(允许1边沿误差),禁止把未启动直接清零。绝对位置和量程仍按Hall上升沿计数。", fill="E8F2FF", color=BLUE)
add_table(
doc,
["场景", "重启行为", "CAN期望"],
[
["正常停止后掉电", "恢复四轴Hall/MaxHall/绝对量,不自动动作", "0x301 PositionValidMask恢复;0x302/0x303与掉电前一致"],
["运动中掉电", "不恢复旧位置;失效轴执行0x0B回最近端", "先PositionValid=0/位置FFFF;归零后Near=1、Hall=0、PositionValid=1"],
["最新槽CRC坏、旧槽有效", "回退到旧的有效clean槽", "恢复旧槽数据,无自动动作"],
["版本不匹配/双槽无效", "保持位置未知,允许0x0B建立原点", "PositionValidMask对应位先为0,归零成功后置1"],
["物理绝对量与Hall不一致", "拒绝恢复,保留CAN以执行0x0B", "PositionValid=0;诊断提示记录无效;首次成功保存后清除"],
],
[1900, 3300, 4160],
font_size=9,
)
def add_setup_and_acceptance(doc):
doc.add_heading("5 台架、仪器与强制安全条件", level=1)
for text in (
"座椅总成可靠固定;四轴全行程无人员、工具、线束与硬限位外障碍。",
"12 V电源具有限流与硬件急停;加热测试增加电流钳和表面温度监控。",
"ZCANPRO + USBCANFD-200U 配置为500 kbit/s Classic CAN,终端电阻按拓扑配置。",
"完整资格测试使用示波器/逻辑分析仪观察PWM与方向/使能;当前无示波器时对应时序项必须记为BLOCKED。",
"第一次动作仅单轴300~500 ms、30%~50%,目视签字确认逻辑轴、机械方向和Hall符号。",
"CAN MotorID 0/1/2/3 必须分别驱动 physical0/1/2/3:水平、坐盆、靠背、腿托;若任一机构不符立即停止。",
):
add_bullet(doc, text)
add_callout(doc, "加热限制", "当前ADC电流估算只用于台架可见性,尚无量产过流阈值标定,不能代替外部限流与温度保护。", fill="FFF4E5", color=RED)
doc.add_heading("5.1 执行顺序", level=2)
for text in (
"全量IAR构建并记录0 error / 0 warning。",
"上电安全与周期反馈;不发送动作命令。",
"通风、加热独立与组合功能。",
"非法帧、查询、重复/旧Sequence与STOP_ALL。",
"四轴低风险正反点动并签字确认方向。",
"单轴定时、同轴换向、单停与四轴并发。",
"首次量程确认使用全行程标定;量程已知但位置无效时只执行0x0B回最近端。",
"正常停止掉电恢复、运动中掉电失效、双槽CRC回退。",
"堵转、驱动故障、CAN bus-off、ADC故障和调试会话安全。",
):
add_number(doc, text)
doc.add_heading("5.2 当前台架回归路线(不再标定、无示波器)", level=2)
add_body(doc, "当前四轴量程已经确认,不重复全轴标定和25/50/75%定位。无示波器不影响CAN反馈、Hall、实物方向、风量、温升和正常掉电恢复,但PWM频率/占空比、换向死区及硬实时关断延迟只能记为BLOCKED,不能根据CAN反馈推定PASS。")
for text in (
"运行04_can_full_function.py:位置有效时直接做热管理和四轴约12%往返;位置无效时先只把失效轴回最近端。",
"在ZCANPRO运行05_seat_function_bridge.py,再双击run_seat_function_gui.bat;按配置表执行12个场景。界面可自动归零失效轴,并保存CAN结果与人工实物观察。",
"先用GUI执行巡航沙发标准场景并停稳,使四轴均处于可信非零位置;再运行02_power_memory.py默认PHASE=CLEAN。倒计时结束后只切断座椅电源至少2秒,电脑/CAN盒保持工作,并验证重启无自动动作及四轴SMV8恢复。",
"运行03_protection_faults.py默认PHASE=AUTO,验证非法帧与Sequence保护。",
"可选MOVING_LOSS先验证M0位置失效,再默认发送0x0B恢复,因此无需重新全量程标定。",
):
add_number(doc, text)
add_callout(doc, "配置表换算冲突", "模式目标按物理值和当前480/374/917/647量程重算,不直接发送表内Hall文本。办公模式M0原表已修正为约346 Hall;当前剩余冲突为女王副驾(行驶)M3 22°应约198 Hall(原表178)。", fill="FFF4E5", color=RED)
doc.add_heading("5.3 核心通过标准", level=2)
add_table(
doc,
["类别", "判定"],
[
["周期", "0x4FA/0x301=100±20 ms;每轴0x302=100±20 ms;每轴0x303=500±50 ms"],
["PWM", "100 Hz±1%;稳态占空比目标±2%;风扇启动增压2.00 s±50 ms"],
["停止", "STOP_MOTOR软件处理≤10 ms;STOP_ALL全部PWM kill≤2 ms,ActiveMask清零≤20 ms"],
["换向", "先全关,死区≥100 ms且Hall安静后反向;桥臂不得重叠"],
["位置", "目标误差≤max(归一化100,2个Hall换算值);Hall符号与机械方向一致"],
["标定", "M0 336~624;M1 261~486;M2 641~1192;M3 452~841"],
["记忆/恢复", "正常重启Hall差≤1;运动中掉电不得恢复旧位置;0x0B后Hall≤2且MaxHall不变"],
["热安全", "加热600 s±1 s安全关闭;ADC连续3次读取错误后关闭;故障反馈=0"],
],
[1800, 7560],
font_size=9,
)
def load_test_vectors():
with CSV_PATH.open("r", encoding="utf-8-sig", newline="") as f:
return list(csv.DictReader(f))
def add_test_catalog(doc):
doc.add_heading("6 完整 CAN/HIL 测试用例目录", level=1)
add_body(doc, "以下用例与配套 CSV 一致。表内“动作”包含发送帧及观察时间;执行时必须在 CSV 的 ActualRx、ActualPhysical、Result、Notes 列回填原始结果。")
groups = OrderedDict()
for row in load_test_vectors():
prefix = row["TestID"].split("-")[0]
groups.setdefault(prefix, []).append(row)
names = {
"SYS": "系统与启动",
"THM": "热管理组合",
"FAN": "座椅通风",
"HEAT": "靠背加热",
"MOV": "四轴移动与协议",
"CAL": "全行程标定",
"HOM": "已知量程回最近端",
"MEM": "位置记忆与掉电",
"FBK": "周期反馈",
"FLT": "故障注入",
}
widths = [800, 650, 1700, 2150, 2300, 1760]
for prefix, rows in groups.items():
doc.add_heading(f"6.{list(groups).index(prefix)+1} {names.get(prefix, prefix)}", level=2)
matrix = []
for row in rows:
tx = row["TxID"]
data = row["TxData_Byte0_to_7"]
action = f"{tx} {data}\n观察: {row['DelayOrObserve']}"
expected = row["ExpectedCAN"]
if row["ExpectedPhysical"]:
expected += f"\n物理: {row['ExpectedPhysical']}"
matrix.append([
row["TestID"], row["Priority"], row["Precondition"],
action, expected, row["Acceptance"],
])
add_table(
doc,
["ID", "优先级", "前置条件", "动作", "预期", "通过标准"],
matrix,
widths,
font_size=7.4,
center_cols=(0, 1),
)
def add_record_sheet(doc):
doc.add_heading("7 测试记录与签署", level=1)
add_body(doc, "每个用例至少保存:发送时间、完整TX/RX帧、机械方向、Hall前后值、PWM频率/占空比、停止延迟、供电电流、结果与异常截图。")
add_table(
doc,
["记录项", "填写内容"],
[
["样件/软件", "样件编号:________________ HEX CRC:________________ 构建日期:________________"],
["台架", "电源/限流:________________ CAN工具:________________ 终端电阻:________________"],
["机械映射", "M0 □ M1 □ M2 □ M3 □ 方向与端点已签字确认"],
["量程实测", "M0:______ M1:______ M2:______ M3:______ Hall"],
["位置记忆", "正常掉电恢复:□PASS □FAIL 运动中掉电失效:□PASS □FAIL 0x0B恢复:□PASS □FAIL"],
["总结果", "□PASS □FAIL □BLOCKED □NOT RUN"],
["备注", "________________________________________________________________________"],
],
[1800, 7560],
font_size=9,
)
add_table(
doc,
["角色", "姓名", "签字", "日期"],
[
["测试执行", "", "", ""],
["软件复核", "", "", ""],
["硬件/安全复核", "", "", ""],
["项目批准", "", "", ""],
],
[1800, 2300, 3000, 2260],
font_size=10,
center_cols=(0, 1, 2, 3),
)
doc.add_heading("附录 A 交付文件", level=1)
for text in (
"Seat_Controller_demo/IAR/rev_d/Exe/Seat_Controller_demo.hex",
"Seat_Controller_demo/Docs/座椅控制器_CAN_测试规范.docx",
"Seat_Controller_demo/Docs/座椅控制器_CAN_测试规范.md",
"Seat_Controller_demo/Docs/座椅控制器_CAN_测试向量.csv",
"Seat_Controller_demo/Tests/manual_hil/README_CN.md",
"Seat_Controller_demo/Tests/manual_hil/04_can_full_function.py",
"Seat_Controller_demo/Tests/manual_hil/05_seat_function_bridge.py",
"Seat_Controller_demo/Tests/manual_hil/run_seat_function_gui.bat",
"量程测试脚本/tests/03_full_range_test",
):
add_bullet(doc, text)
doc.add_heading("附录 B 修订记录", level=1)
add_table(
doc,
["版本", "日期", "变更"],
[
["1.0", "2026-07-28", "统一通风、加热、移动的基础 CAN/HIL 规范"],
["2.0", "2026-07-29", "纳入四轴实测量程;修正端点/方向;新增 SMV7 每次移动结束位置记忆与掉电测试;废弃 SMV5/SMV6 旧量程记录"],
["2.1", "2026-07-30", "按四轴量程日志改为 MotorID/physical Mode 同号映射;SMV8 禁止恢复旧通道语义;新增四组手动 HIL 日志脚本"],
["2.2", "2026-07-30", "当前回归不再标定且无示波器;新增配置表模式选择界面、ZCANPRO桥接、量程重算冲突提示和三组必做流程"],
["2.3", "2026-07-30", "新增HOME_KNOWN_RANGE(0x0B):位置未知时仅回最近端建立Hall=0,保留确认量程;脚本自动恢复后继续目标"],
],
[1000, 1600, 6760],
font_size=9,
center_cols=(0, 1),
)
def build():
doc = Document()
configure_document(doc)
add_cover(doc)
add_overview(doc)
add_calibration(doc)
add_can_protocol(doc)
add_persistence(doc)
add_setup_and_acceptance(doc)
add_test_catalog(doc)
add_record_sheet(doc)
core = doc.core_properties
core.title = "Seat_Controller_demo 座椅通风、加热与四轴移动 CAN/HIL 测试规范"
core.subject = "CAN/HIL test specification with SMV8 position persistence"
core.author = "Seat Controller Engineering"
core.keywords = "Seat, CAN, HIL, Hall, Calibration, SMV8"
core.comments = "Generated from the verified Seat_Controller_demo protocol and test-vector sources."
doc.save(OUTPUT_PATH)
print(OUTPUT_PATH)
if __name__ == "__main__":
build()
@@ -0,0 +1,76 @@
TestID,Module,Priority,Precondition,TxID,DLC,TxData_Byte0_to_7,DelayOrObserve,ExpectedRxID,ExpectedCAN,ExpectedPhysical,Acceptance,ActualRx,ActualPhysical,Result,Notes
SYS-001,Build,P0,IAR EWARM 9.2 available,N/A,N/A,N/A,N/A,N/A,CYT4BFBCJE and CY_MCU_rev_d and Cortex-M0+ Flash,No hardware action,0 errors and 0 warnings,,,,
SYS-002,Boot,P0,Cold POR without debugger and outputs monitored,N/A,N/A,N/A,2 s,0x301/0x4FA,System READY and ActiveMask=0; thermal feedback Byte0=09,All motor/fan/heater outputs initially low,No debugger is required for normal CAN operation,,,,
SYS-003,Debug safety,P0,Download and Debug active then end C-SPY,N/A,N/A,N/A,20 ms,0x301 or bus stops,System fault/inhibit if still running,All PWM and direction outputs low,Outputs low within 20 ms and stay low until genuine POR,,,,
THM-001,Thermal off,P0,System booted,0x29E,8,22 00 00 00 00 00 00 00,200 ms,0x4FA,Byte0=09,Fan and heater off,Feedback period 100 ms ±20 ms,,,,
FAN-001,Fan gear1,P0,THM-001 passed,0x29E,8,25 00 01 00 00 00 00 00,2.3 s,0x4FA,Byte0=0A,P11.1 100 Hz; 100% for 2 s then 50%,Frequency ±1%; duty ±2%; startup 2.00 s ±50 ms,,,,
FAN-002,Fan gear2,P0,FAN-001 running,0x29E,8,25 00 02 00 00 00 00 00,300 ms,0x4FA,Byte0=0B,P11.1 changes to 70% without another startup boost,Duty 70% ±2%,,,,
FAN-003,Fan gear3,P0,FAN-002 running,0x29E,8,25 00 03 00 00 00 00 00,300 ms,0x4FA,Byte0=0C,P11.1 100%,Duty 100% ±2%,,,,
FAN-004,Fan vehicle gear5 clamp,P1,FAN running,0x29E,8,25 00 05 00 00 00 00 00,300 ms,0x4FA,Byte0=0C,Physical gear remains3 / 100%,Clamp and feedback agree,,,,
FAN-005,Fan switch off,P0,FAN-004 running,0x42A,8,02 00 00 00 00 00 00 00,100 ms,0x4FA,Fan field=1,P11.1 GPIO low,Fan stops within 10 ms,,,,
FAN-006,Unused output,P0,All fan tests,N/A,N/A,N/A,Continuous,N/A,N/A,P11.2 remains GPIO low,No pulse or high glitch observed,,,,
THM-002,Configure max gear2,P1,Outputs off,0x2AA,8,01 00 12 00 00 00 00 00,20 ms,N/A,Valid config accepted,No output starts,No thermal invalid counter increment,,,,
THM-003,Config clamp combined,P1,THM-002 and heater mode valid,0x3CE,8,10 00 00 00 00 00 00 00,10 ms,N/A,Heater mode valid,No output change,N/A,,,,
THM-004,Config clamp combined,P1,THM-003,0x29E,8,15 00 33 00 00 00 00 00,2.3 s,0x4FA,Byte0=1B,Fan and heater both clamp to physical level2,Fan70% and heater60% ±2%,,,,
HEAT-001,Heater mode,P0,Outputs off,0x3CE,8,10 00 00 00 00 00 00 00,20 ms,N/A,Mode=1 accepted,Heater stays off,No invalid count increment,,,,
HEAT-002,Heater gear1,P0,HEAT-001; max config reset to3 via 0x2AA Byte2=1B,0x29E,8,12 00 10 00 00 00 00 00,300 ms,0x4FA,Byte0=11,P16.7 100 Hz 30%,Frequency ±1%; duty ±2%,,,,
HEAT-003,Heater gear2,P0,HEAT-002,0x29E,8,12 00 20 00 00 00 00 00,300 ms,0x4FA,Byte0=19,P16.7 60%,Duty ±2%,,,,
HEAT-004,Heater gear3,P0,HEAT-003,0x29E,8,12 00 30 00 00 00 00 00,300 ms,0x4FA,Byte0=21,P16.7 90%,Duty ±2%,,,,
HEAT-005,Heater invalid mode,P0,Heater running,0x3CE,8,00 00 00 00 00 00 00 00,100 ms,0x4FA,Heater field=0,P16.7 low,Invalid count increments and heater stops,,,,
HEAT-006,Heater timeout,P1,Valid mode and gear1 active,0x29E,8,12 00 10 00 00 00 00 00,605 s,0x4FA,Heater field=0 after timeout,P16.7 low at 600 s,Timeout 600 s ±1 s,,,,Long duration
THM-005,Combined levels,P0,Valid mode and max3,0x29E,8,15 00 32 00 00 00 00 00,2.3 s,0x4FA,Byte0=23,Fan gear2 and heater gear3,Fan70%; heater90% ±2%,,,,
THM-006,Combined off,P0,THM-005 running,0x42A,8,0A 00 00 00 00 00 00 00,100 ms,0x4FA,Byte0=09,Fan and heater low,Both stop within 10 ms,,,,
THM-007,Bad DLC fail-safe,P0,Outputs running,0x29E,7,25 00 02 00 00 00 00,100 ms,0x4FA,Byte0=00,Fan and heater low,Invalid frame cannot leave an output active,,,,
MOV-001,STOP_ALL baseline,P0,System running,0x300,8,09 10 00 01 00 00 00 00,20 ms,0x301,Ack=4; Seq=0x0100; Opcode=09; ActiveMask=0,All four motors off,PWM kill ≤2 ms; feedback ≤20 ms,,,,
MOV-002,QUERY M0,P0,MOV-001,0x300,8,0C 10 01 01 00 00 00 00,20 ms,0x301/0x302/0x303,Ack=1; Seq=0x0101; M0 status and diagnostic,No movement,All three frames within 20 ms,,,,
MOV-003,Bad version,P0,MOV-002,0x300,8,0C 00 02 01 00 00 00 00,20 ms,0x301,Ack=2; Reject=2 BAD_VERSION,No movement,Outputs and tasks unchanged,,,,
MOV-004,Bad opcode,P0,MOV-003,0x300,8,04 10 03 01 00 00 00 00,20 ms,0x301,Ack=2; Reject=3 BAD_OPCODE,No movement,Outputs unchanged,,,,
MOV-005,Bad STOP_ALL motor,P0,MOV-004,0x300,8,09 11 04 01 00 00 00 00,20 ms,0x301,Ack=2; Reject=4 BAD_MOTOR,No movement,Outputs unchanged,,,,
MOV-006,Bad direction,P0,MOV-005,0x300,8,01 14 05 01 88 13 40 1F,20 ms,0x301,Ack=2; Reject=5 BAD_DIRECTION,No movement,Outputs unchanged,,,,
MOV-007,Bad timed range,P0,MOV-006,0x300,8,03 14 06 01 63 00 20 03,20 ms,0x301,Ack=2; Reject=6 PARAM_RANGE,No movement,Duration 99 ms rejected,,,,
MOV-008,Uncalibrated position,P0,Target axis PositionValid=0,0x300,8,01 10 07 01 88 13 40 1F,20 ms,0x301/0x302,Ack=2; Reject=7 or13; position=FFFF,No movement,Position command rejected,,,,
MOV-009,M0 toward-far jog,P0,Axis mapping visually safe,0x300,8,03 14 08 01 F4 01 F4 01,700 ms,0x301/0x302/0x303,Ack=1; TIMED_RUN then COMPLETE/OK; Seq=0x0108,Seat moves rearward toward100%; Hall increases,500 ms ±50 ms; only physical0 driven,,,,
MOV-010,M0 toward-near jog,P0,MOV-009,0x300,8,03 18 09 01 F4 01 F4 01,700 ms,0x302/0x303,TIMED_RUN then COMPLETE/OK,Seat moves forward toward0%; Hall decreases,Direction and Hall sign correct,,,,
MOV-011,M1 toward-far jog,P0,physical0 rewiring visually confirmed,0x300,8,03 15 0A 01 F4 01 F4 01,700 ms,0x302/0x303,M1 only; Seq=0x010A,Seat pan moves diagonally downward; Hall increases,No old leg-extension mechanism motion,,,,
MOV-012,M1 toward-near jog,P0,MOV-011,0x300,8,03 19 0B 01 F4 01 F4 01,700 ms,0x302/0x303,M1 only,Seat pan moves diagonally upward; Hall decreases,Mapping signed off,,,,
MOV-013,M2 toward-far jog,P0,M2 connected,0x300,8,03 16 0C 01 F4 01 BC 02,700 ms,0x302/0x303,M2 only,Backrest reclines; Hall increases,Mapping signed off,,,,
MOV-014,M2 toward-near jog,P0,MOV-013,0x300,8,03 1A 0D 01 F4 01 BC 02,700 ms,0x302/0x303,M2 only,Backrest moves toward upright; Hall decreases,Mapping signed off,,,,
MOV-015,M3 positive jog,P0,M3 connected and clear,0x300,8,03 17 0E 01 F4 01 F4 01,700 ms,0x302/0x303,M3 only,Legrest raises; Hall increases,Mapping signed off or BLOCKED if not connected,,,,
MOV-016,M3 negative jog,P0,MOV-015,0x300,8,03 1B 0F 01 F4 01 F4 01,700 ms,0x302/0x303,M3 only,Legrest lowers; Hall decreases,Mapping signed off or BLOCKED,,,,
MOV-017,Duplicate sequence,P0,MOV-016 complete,0x300,8,0C 10 10 01 00 00 00 00,20 ms,0x301,Ack=1 for first query,No movement,Store first response then repeat next row,,,,
MOV-018,Duplicate sequence,P0,MOV-017,0x300,8,0C 10 10 01 00 00 00 00,20 ms,0x301,Ack=3 DUPLICATE; Seq=0x0110,No movement,No second task/action,,,,
MOV-019,Old sequence,P0,MOV-018,0x300,8,0C 10 0F 01 00 00 00 00,20 ms,0x301,Ack=2; Reject=8 SEQUENCE_OLD,No movement,State unchanged,,,,
MOV-020,Old sequence STOP_ALL,P0,At least one motor active,0x300,8,09 10 01 00 00 00 00 00,20 ms,0x301,Ack=4 STOP_ALL_ACCEPTED; ActiveMask=0,All motors off,Old STOP_ALL still kills within2 ms,,,,
MOV-021,Concurrent start M0,P0,Four mappings signed off,0x300,8,03 14 00 02 B8 0B 20 03,120 ms,0x301/0x302,M0 active,Horizontal moving positive,Continue immediately with next axes,,,,
MOV-022,Concurrent start M1,P0,MOV-021 active,0x300,8,03 15 01 02 B8 0B 20 03,120 ms,0x301/0x302,M0 and M1 active,Two intended axes move,No M0 Sequence/state change,,,,
MOV-023,Concurrent start M2,P0,MOV-022 active,0x300,8,03 16 02 02 B8 0B BC 02,120 ms,0x301/0x302,ActiveMask includes M0-M2,Three intended axes move,Independent Hall increments,,,,
MOV-024,Concurrent start M3,P0,MOV-023 active and power budget safe,0x300,8,03 17 03 02 B8 0B 20 03,300 ms,0x301/0x302,ActiveMask=0F,Four intended axes move,No cross-axis cancellation or wrong output,,,,
MOV-025,STOP_MOTOR M2,P0,MOV-024 active,0x300,8,08 12 04 02 00 00 00 00,50 ms,0x301/0x302,M2 Result=STOPPED_BY_CMD; other active bits stay,M2 stops only,Stop ≤10 ms; other Hall continues,,,,
MOV-026,STOP_ALL four axes,P0,MOV-025,0x300,8,09 10 05 02 00 00 00 00,20 ms,0x301,Ack=4; ActiveMask=0,All axes stop,Kill ≤2 ms,,,,
MOV-027,Same-axis reverse deadtime,P0,M0 positive 3 s command active,0x300,8,03 18 06 02 E8 03 20 03,1500 ms,0x302,M0 eventually negative; no other sequence changes,M0 output all-off then negative,Deadtime ≥100 ms and no bridge overlap,,,,
MOV-028,Absolute 50 percent,P1,Target axis calibrated and at safe position,0x300,8,01 10 07 02 88 13 40 1F,15 s,0x302,POSITION_RUN then COMPLETE/OK; position near5000,M0 reaches50%,Error ≤max(100 raw; two Hall pulses),,,,
MOV-029,Relative minus10 percent,P1,MOV-028 complete,0x300,8,02 10 08 02 18 FC 40 1F,15 s,0x302,COMPLETE/OK; final near4000,M0 moves -10%,Same position tolerance,,,,
MOV-030,M1 absolute 50 percent,P1,M1 calibrated and full path clear,0x300,8,01 11 09 02 88 13 40 1F,15 s,0x302/0x303,COMPLETE/OK; position near5000; Hall near187,Seat pan absolute displacement H25.5 mm and V28 mm,Error ≤max(100 raw; two Hall pulses),,,,
MOV-031,M2 absolute 50 percent,P1,M2 calibrated and full path clear,0x300,8,01 12 0A 02 88 13 E0 2E,15 s,0x302/0x303,COMPLETE/OK; position near5000; Hall near459,Backrest absolute angle near34 degrees,Error ≤max(100 raw; two Hall pulses),,,,
MOV-032,M3 absolute 50 percent,P1,M3 calibrated and full path clear,0x300,8,01 13 0B 02 88 13 10 27,15 s,0x302/0x303,COMPLETE/OK; position near5000; Hall near324,Legrest absolute angle near36 degrees,Error ≤max(100 raw; two Hall pulses),,,,
CAL-001,Calibration bad key,P0,All axes stopped,0x300,8,0A 10 09 02 00 00 78 00,20 ms,0x301,Ack=2; Reject=6 PARAM_RANGE,No movement,Invalid key cannot start calibration,,,,
CAL-002,Calibration bad timeout,P0,All axes stopped,0x300,8,0A 10 0A 02 5A A5 09 00,20 ms,0x301,Ack=2; Reject=6 PARAM_RANGE,No movement,9 s rejected,,,,
CAL-003,Calibration M0,P0,Full travel safe and all axes stopped,0x300,8,0A 10 0B 02 5A A5 78 00,Until complete,0x301/0x302/0x303,SystemState=CALIBRATION; periodic frames continue; final OK,M0 seeks front then rear endpoints,Each leg ≤120 s; MaxHall 336-624; saved CRC,,,,Mechanical full travel
CAL-004,STOP during calibration,P0,CALIBRATION active,0x300,8,09 10 01 00 00 00 00 00,50 ms,0x301/0x302,Ack=4; ActiveMask=0; no false OK,Motor stops immediately,Kill ≤2 ms; incomplete range not saved,,,,Destructive sequence reset
CAL-005,Four-axis calibration,P0,SMV8 absent or erased; full mechanism safe,0x300,8,M0-M3 sequential 0A 10/11/12/13 SeqLE 5A A5 78 00,Up to 10 min,0x301/0x302/0x303,PositionValidMask progresses to0F; final MaxHall near 480/374/917/647,Axes calibrate one at a time; MotorID equals physical Mode,M0 336-624; M1 261-486; M2 641-1192; M3 452-841,,,,Mechanical full travel
CAL-006,Calibration persistence,P0,CAL-005 passed,Power cycle,N/A,N/A,5 s,0x301/0x303,PositionValidMask=0F; MaxHall values unchanged,No automatic repeated full travel,Valid SMV8 restores all four positions,,,,
CAL-007,One valid one old slot,P1,Inject one valid SMV8 slot and one old/nonblank slot,Power cycle,N/A,N/A,5 s,0x301/0x303,PositionValid from valid slot; no CAL_DATA_INVALID,No automatic motion,No false corrupt report,,,,Bench flash fixture
HOM-001,Known-range home bad key,P0,All axes stopped,0x300,8,0B 10 0C 02 00 00 78 00,20 ms,0x301,Ack=2; Reject=6 PARAM_RANGE,No movement,Wrong safety key cannot start homing,,,,
HOM-002,M0 known-range near home,P0,M0 PositionValid=0; front travel clear; confirmed MaxHall=480,0x300,8,0B 10 0D 02 52 4B 78 00,Until complete,0x301/0x302/0x303,Ack=1; CALIBRATION then COMPLETE/OK; Near=1; PositionValid=1; Hall<=2; MaxHall=480,Seat moves only to the front endpoint,Does not visit rear endpoint or alter MaxHall,,,,Mechanical near-end travel
HOM-003,Four-axis invalid-only recovery,P0,At least one invalid axis; all near-end paths clear,0x300,8,Send 0B only for invalid M0-M3 with ParamA=524B and ParamB=7800,Until complete,0x301/0x302/0x303,PositionValidMask progresses to0F; each recovered axis Hall<=2; MaxHall 480/374/917/647,Only invalid axes move to their defined near endpoints,Already-valid axes do not move; no far-end measurement,,,,Mechanical near-end travel
HOM-004,Absolute target after recovery,P0,HOM-003 passed,0x300,8,01 10 12 02 B0 04 30 75,Until complete,0x302/0x303,M0 COMPLETE/OK near1200 normalized and about58 Hall,Seat moves from front endpoint to about25.8 mm,Target error <=max(100 raw; two Hall pulses),,,,
MEM-001,Save after absolute move,P0,All axes calibrated; record 0x302/0x303 before move,0x300,8,01 10 20 03 88 13 40 1F,After all motors settle,0x302/0x303,M0 position near5000; Hall near240; MaxHall near480,M0 near107.5 mm,Position saved only after coast/motion is fully stopped,,,,
MEM-002,Restore last stopped position,P0,MEM-001 passed and power removed only after completion,Power cycle,N/A,N/A,5 s,0x301/0x302/0x303,PositionValidMask=0F; M0 position/Hall equal pre-power values,No motor automatically moves,Hall difference ≤1 and normalized difference ≤2 Hall equivalents,,,,
MEM-003,Save after timed stop,P0,All axes calibrated,0x300,8,03 17 21 03 F4 01 F4 01,After completion then power cycle,0x302/0x303,M3 Hall/position restore to the value captured at stop,Legrest remains still,Restored values equal final pre-power feedback within1 Hall,,,,
MEM-004,Power loss during movement,P0,All axes calibrated and M0 moving,Remove power while active,N/A,N/A,Power cycle,0x301/0x302/0x303,M0 PositionValid=0; normalized position=FFFF,No automatic movement,Old pre-motion position must not be restored; then HOM-002 restores the origin,,,,Destructive power-cycle test
MEM-005,Dual-slot CRC fallback,P1,Create newest corrupt slot and older valid clean SMV8 slot,Power cycle,N/A,N/A,5 s,0x301/0x302/0x303,Older valid clean state restored,No automatic movement,Position and MaxHall match valid slot,,,,Bench flash fixture
FBK-001,Feedback periods,P0,System idle,N/A,N/A,N/A,2 s,0x301/0x302/0x303,All MotorID 0-3 present and Alive continuous,No movement,301=100±20 ms; per-axis302=100±20 ms;303=500±50 ms,,,,
FLT-001,Hall stall isolation,P1,Two axes active; safely block target axis,N/A,N/A,N/A,Until fault,0x301/0x302/0x303,Only target FaultedMask bit; STALL result/flag,Target output off; other axis follows policy,No wrong-axis stop or fault bit,,,,Use safe low current
FLT-002,CAN bus-off,P0,At least one output active,Disconnect/force bus-off,N/A,N/A,20 ms,N/A,Bus-off counter/fault latched,All outputs low,Safe-off within20 ms,,,,
FLT-003,Heater ADC read fault,P1,Heater active; inject ADC read errors,N/A,N/A,N/A,50 ms,0x4FA,Heater feedback=0,P16.7 low,Three consecutive 10 ms sample errors stop heat,,,,
FLT-004,Initialization failure,P0,Force selected peripheral init failure then reset,N/A,N/A,N/A,100 ms,N/A,No active feedback,All motor/fan/heater outputs low,No transient start,,,,
1 TestID Module Priority Precondition TxID DLC TxData_Byte0_to_7 DelayOrObserve ExpectedRxID ExpectedCAN ExpectedPhysical Acceptance ActualRx ActualPhysical Result Notes
2 SYS-001 Build P0 IAR EWARM 9.2 available N/A N/A N/A N/A N/A CYT4BFBCJE and CY_MCU_rev_d and Cortex-M0+ Flash No hardware action 0 errors and 0 warnings
3 SYS-002 Boot P0 Cold POR without debugger and outputs monitored N/A N/A N/A 2 s 0x301/0x4FA System READY and ActiveMask=0; thermal feedback Byte0=09 All motor/fan/heater outputs initially low No debugger is required for normal CAN operation
4 SYS-003 Debug safety P0 Download and Debug active then end C-SPY N/A N/A N/A 20 ms 0x301 or bus stops System fault/inhibit if still running All PWM and direction outputs low Outputs low within 20 ms and stay low until genuine POR
5 THM-001 Thermal off P0 System booted 0x29E 8 22 00 00 00 00 00 00 00 200 ms 0x4FA Byte0=09 Fan and heater off Feedback period 100 ms ±20 ms
6 FAN-001 Fan gear1 P0 THM-001 passed 0x29E 8 25 00 01 00 00 00 00 00 2.3 s 0x4FA Byte0=0A P11.1 100 Hz; 100% for 2 s then 50% Frequency ±1%; duty ±2%; startup 2.00 s ±50 ms
7 FAN-002 Fan gear2 P0 FAN-001 running 0x29E 8 25 00 02 00 00 00 00 00 300 ms 0x4FA Byte0=0B P11.1 changes to 70% without another startup boost Duty 70% ±2%
8 FAN-003 Fan gear3 P0 FAN-002 running 0x29E 8 25 00 03 00 00 00 00 00 300 ms 0x4FA Byte0=0C P11.1 100% Duty 100% ±2%
9 FAN-004 Fan vehicle gear5 clamp P1 FAN running 0x29E 8 25 00 05 00 00 00 00 00 300 ms 0x4FA Byte0=0C Physical gear remains3 / 100% Clamp and feedback agree
10 FAN-005 Fan switch off P0 FAN-004 running 0x42A 8 02 00 00 00 00 00 00 00 100 ms 0x4FA Fan field=1 P11.1 GPIO low Fan stops within 10 ms
11 FAN-006 Unused output P0 All fan tests N/A N/A N/A Continuous N/A N/A P11.2 remains GPIO low No pulse or high glitch observed
12 THM-002 Configure max gear2 P1 Outputs off 0x2AA 8 01 00 12 00 00 00 00 00 20 ms N/A Valid config accepted No output starts No thermal invalid counter increment
13 THM-003 Config clamp combined P1 THM-002 and heater mode valid 0x3CE 8 10 00 00 00 00 00 00 00 10 ms N/A Heater mode valid No output change N/A
14 THM-004 Config clamp combined P1 THM-003 0x29E 8 15 00 33 00 00 00 00 00 2.3 s 0x4FA Byte0=1B Fan and heater both clamp to physical level2 Fan70% and heater60% ±2%
15 HEAT-001 Heater mode P0 Outputs off 0x3CE 8 10 00 00 00 00 00 00 00 20 ms N/A Mode=1 accepted Heater stays off No invalid count increment
16 HEAT-002 Heater gear1 P0 HEAT-001; max config reset to3 via 0x2AA Byte2=1B 0x29E 8 12 00 10 00 00 00 00 00 300 ms 0x4FA Byte0=11 P16.7 100 Hz 30% Frequency ±1%; duty ±2%
17 HEAT-003 Heater gear2 P0 HEAT-002 0x29E 8 12 00 20 00 00 00 00 00 300 ms 0x4FA Byte0=19 P16.7 60% Duty ±2%
18 HEAT-004 Heater gear3 P0 HEAT-003 0x29E 8 12 00 30 00 00 00 00 00 300 ms 0x4FA Byte0=21 P16.7 90% Duty ±2%
19 HEAT-005 Heater invalid mode P0 Heater running 0x3CE 8 00 00 00 00 00 00 00 00 100 ms 0x4FA Heater field=0 P16.7 low Invalid count increments and heater stops
20 HEAT-006 Heater timeout P1 Valid mode and gear1 active 0x29E 8 12 00 10 00 00 00 00 00 605 s 0x4FA Heater field=0 after timeout P16.7 low at 600 s Timeout 600 s ±1 s Long duration
21 THM-005 Combined levels P0 Valid mode and max3 0x29E 8 15 00 32 00 00 00 00 00 2.3 s 0x4FA Byte0=23 Fan gear2 and heater gear3 Fan70%; heater90% ±2%
22 THM-006 Combined off P0 THM-005 running 0x42A 8 0A 00 00 00 00 00 00 00 100 ms 0x4FA Byte0=09 Fan and heater low Both stop within 10 ms
23 THM-007 Bad DLC fail-safe P0 Outputs running 0x29E 7 25 00 02 00 00 00 00 100 ms 0x4FA Byte0=00 Fan and heater low Invalid frame cannot leave an output active
24 MOV-001 STOP_ALL baseline P0 System running 0x300 8 09 10 00 01 00 00 00 00 20 ms 0x301 Ack=4; Seq=0x0100; Opcode=09; ActiveMask=0 All four motors off PWM kill ≤2 ms; feedback ≤20 ms
25 MOV-002 QUERY M0 P0 MOV-001 0x300 8 0C 10 01 01 00 00 00 00 20 ms 0x301/0x302/0x303 Ack=1; Seq=0x0101; M0 status and diagnostic No movement All three frames within 20 ms
26 MOV-003 Bad version P0 MOV-002 0x300 8 0C 00 02 01 00 00 00 00 20 ms 0x301 Ack=2; Reject=2 BAD_VERSION No movement Outputs and tasks unchanged
27 MOV-004 Bad opcode P0 MOV-003 0x300 8 04 10 03 01 00 00 00 00 20 ms 0x301 Ack=2; Reject=3 BAD_OPCODE No movement Outputs unchanged
28 MOV-005 Bad STOP_ALL motor P0 MOV-004 0x300 8 09 11 04 01 00 00 00 00 20 ms 0x301 Ack=2; Reject=4 BAD_MOTOR No movement Outputs unchanged
29 MOV-006 Bad direction P0 MOV-005 0x300 8 01 14 05 01 88 13 40 1F 20 ms 0x301 Ack=2; Reject=5 BAD_DIRECTION No movement Outputs unchanged
30 MOV-007 Bad timed range P0 MOV-006 0x300 8 03 14 06 01 63 00 20 03 20 ms 0x301 Ack=2; Reject=6 PARAM_RANGE No movement Duration 99 ms rejected
31 MOV-008 Uncalibrated position P0 Target axis PositionValid=0 0x300 8 01 10 07 01 88 13 40 1F 20 ms 0x301/0x302 Ack=2; Reject=7 or13; position=FFFF No movement Position command rejected
32 MOV-009 M0 toward-far jog P0 Axis mapping visually safe 0x300 8 03 14 08 01 F4 01 F4 01 700 ms 0x301/0x302/0x303 Ack=1; TIMED_RUN then COMPLETE/OK; Seq=0x0108 Seat moves rearward toward100%; Hall increases 500 ms ±50 ms; only physical0 driven
33 MOV-010 M0 toward-near jog P0 MOV-009 0x300 8 03 18 09 01 F4 01 F4 01 700 ms 0x302/0x303 TIMED_RUN then COMPLETE/OK Seat moves forward toward0%; Hall decreases Direction and Hall sign correct
34 MOV-011 M1 toward-far jog P0 physical0 rewiring visually confirmed 0x300 8 03 15 0A 01 F4 01 F4 01 700 ms 0x302/0x303 M1 only; Seq=0x010A Seat pan moves diagonally downward; Hall increases No old leg-extension mechanism motion
35 MOV-012 M1 toward-near jog P0 MOV-011 0x300 8 03 19 0B 01 F4 01 F4 01 700 ms 0x302/0x303 M1 only Seat pan moves diagonally upward; Hall decreases Mapping signed off
36 MOV-013 M2 toward-far jog P0 M2 connected 0x300 8 03 16 0C 01 F4 01 BC 02 700 ms 0x302/0x303 M2 only Backrest reclines; Hall increases Mapping signed off
37 MOV-014 M2 toward-near jog P0 MOV-013 0x300 8 03 1A 0D 01 F4 01 BC 02 700 ms 0x302/0x303 M2 only Backrest moves toward upright; Hall decreases Mapping signed off
38 MOV-015 M3 positive jog P0 M3 connected and clear 0x300 8 03 17 0E 01 F4 01 F4 01 700 ms 0x302/0x303 M3 only Legrest raises; Hall increases Mapping signed off or BLOCKED if not connected
39 MOV-016 M3 negative jog P0 MOV-015 0x300 8 03 1B 0F 01 F4 01 F4 01 700 ms 0x302/0x303 M3 only Legrest lowers; Hall decreases Mapping signed off or BLOCKED
40 MOV-017 Duplicate sequence P0 MOV-016 complete 0x300 8 0C 10 10 01 00 00 00 00 20 ms 0x301 Ack=1 for first query No movement Store first response then repeat next row
41 MOV-018 Duplicate sequence P0 MOV-017 0x300 8 0C 10 10 01 00 00 00 00 20 ms 0x301 Ack=3 DUPLICATE; Seq=0x0110 No movement No second task/action
42 MOV-019 Old sequence P0 MOV-018 0x300 8 0C 10 0F 01 00 00 00 00 20 ms 0x301 Ack=2; Reject=8 SEQUENCE_OLD No movement State unchanged
43 MOV-020 Old sequence STOP_ALL P0 At least one motor active 0x300 8 09 10 01 00 00 00 00 00 20 ms 0x301 Ack=4 STOP_ALL_ACCEPTED; ActiveMask=0 All motors off Old STOP_ALL still kills within2 ms
44 MOV-021 Concurrent start M0 P0 Four mappings signed off 0x300 8 03 14 00 02 B8 0B 20 03 120 ms 0x301/0x302 M0 active Horizontal moving positive Continue immediately with next axes
45 MOV-022 Concurrent start M1 P0 MOV-021 active 0x300 8 03 15 01 02 B8 0B 20 03 120 ms 0x301/0x302 M0 and M1 active Two intended axes move No M0 Sequence/state change
46 MOV-023 Concurrent start M2 P0 MOV-022 active 0x300 8 03 16 02 02 B8 0B BC 02 120 ms 0x301/0x302 ActiveMask includes M0-M2 Three intended axes move Independent Hall increments
47 MOV-024 Concurrent start M3 P0 MOV-023 active and power budget safe 0x300 8 03 17 03 02 B8 0B 20 03 300 ms 0x301/0x302 ActiveMask=0F Four intended axes move No cross-axis cancellation or wrong output
48 MOV-025 STOP_MOTOR M2 P0 MOV-024 active 0x300 8 08 12 04 02 00 00 00 00 50 ms 0x301/0x302 M2 Result=STOPPED_BY_CMD; other active bits stay M2 stops only Stop ≤10 ms; other Hall continues
49 MOV-026 STOP_ALL four axes P0 MOV-025 0x300 8 09 10 05 02 00 00 00 00 20 ms 0x301 Ack=4; ActiveMask=0 All axes stop Kill ≤2 ms
50 MOV-027 Same-axis reverse deadtime P0 M0 positive 3 s command active 0x300 8 03 18 06 02 E8 03 20 03 1500 ms 0x302 M0 eventually negative; no other sequence changes M0 output all-off then negative Deadtime ≥100 ms and no bridge overlap
51 MOV-028 Absolute 50 percent P1 Target axis calibrated and at safe position 0x300 8 01 10 07 02 88 13 40 1F 15 s 0x302 POSITION_RUN then COMPLETE/OK; position near5000 M0 reaches50% Error ≤max(100 raw; two Hall pulses)
52 MOV-029 Relative minus10 percent P1 MOV-028 complete 0x300 8 02 10 08 02 18 FC 40 1F 15 s 0x302 COMPLETE/OK; final near4000 M0 moves -10% Same position tolerance
53 MOV-030 M1 absolute 50 percent P1 M1 calibrated and full path clear 0x300 8 01 11 09 02 88 13 40 1F 15 s 0x302/0x303 COMPLETE/OK; position near5000; Hall near187 Seat pan absolute displacement H25.5 mm and V28 mm Error ≤max(100 raw; two Hall pulses)
54 MOV-031 M2 absolute 50 percent P1 M2 calibrated and full path clear 0x300 8 01 12 0A 02 88 13 E0 2E 15 s 0x302/0x303 COMPLETE/OK; position near5000; Hall near459 Backrest absolute angle near34 degrees Error ≤max(100 raw; two Hall pulses)
55 MOV-032 M3 absolute 50 percent P1 M3 calibrated and full path clear 0x300 8 01 13 0B 02 88 13 10 27 15 s 0x302/0x303 COMPLETE/OK; position near5000; Hall near324 Legrest absolute angle near36 degrees Error ≤max(100 raw; two Hall pulses)
56 CAL-001 Calibration bad key P0 All axes stopped 0x300 8 0A 10 09 02 00 00 78 00 20 ms 0x301 Ack=2; Reject=6 PARAM_RANGE No movement Invalid key cannot start calibration
57 CAL-002 Calibration bad timeout P0 All axes stopped 0x300 8 0A 10 0A 02 5A A5 09 00 20 ms 0x301 Ack=2; Reject=6 PARAM_RANGE No movement 9 s rejected
58 CAL-003 Calibration M0 P0 Full travel safe and all axes stopped 0x300 8 0A 10 0B 02 5A A5 78 00 Until complete 0x301/0x302/0x303 SystemState=CALIBRATION; periodic frames continue; final OK M0 seeks front then rear endpoints Each leg ≤120 s; MaxHall 336-624; saved CRC Mechanical full travel
59 CAL-004 STOP during calibration P0 CALIBRATION active 0x300 8 09 10 01 00 00 00 00 00 50 ms 0x301/0x302 Ack=4; ActiveMask=0; no false OK Motor stops immediately Kill ≤2 ms; incomplete range not saved Destructive sequence reset
60 CAL-005 Four-axis calibration P0 SMV8 absent or erased; full mechanism safe 0x300 8 M0-M3 sequential 0A 10/11/12/13 SeqLE 5A A5 78 00 Up to 10 min 0x301/0x302/0x303 PositionValidMask progresses to0F; final MaxHall near 480/374/917/647 Axes calibrate one at a time; MotorID equals physical Mode M0 336-624; M1 261-486; M2 641-1192; M3 452-841 Mechanical full travel
61 CAL-006 Calibration persistence P0 CAL-005 passed Power cycle N/A N/A 5 s 0x301/0x303 PositionValidMask=0F; MaxHall values unchanged No automatic repeated full travel Valid SMV8 restores all four positions
62 CAL-007 One valid one old slot P1 Inject one valid SMV8 slot and one old/nonblank slot Power cycle N/A N/A 5 s 0x301/0x303 PositionValid from valid slot; no CAL_DATA_INVALID No automatic motion No false corrupt report Bench flash fixture
63 HOM-001 Known-range home bad key P0 All axes stopped 0x300 8 0B 10 0C 02 00 00 78 00 20 ms 0x301 Ack=2; Reject=6 PARAM_RANGE No movement Wrong safety key cannot start homing
64 HOM-002 M0 known-range near home P0 M0 PositionValid=0; front travel clear; confirmed MaxHall=480 0x300 8 0B 10 0D 02 52 4B 78 00 Until complete 0x301/0x302/0x303 Ack=1; CALIBRATION then COMPLETE/OK; Near=1; PositionValid=1; Hall<=2; MaxHall=480 Seat moves only to the front endpoint Does not visit rear endpoint or alter MaxHall Mechanical near-end travel
65 HOM-003 Four-axis invalid-only recovery P0 At least one invalid axis; all near-end paths clear 0x300 8 Send 0B only for invalid M0-M3 with ParamA=524B and ParamB=7800 Until complete 0x301/0x302/0x303 PositionValidMask progresses to0F; each recovered axis Hall<=2; MaxHall 480/374/917/647 Only invalid axes move to their defined near endpoints Already-valid axes do not move; no far-end measurement Mechanical near-end travel
66 HOM-004 Absolute target after recovery P0 HOM-003 passed 0x300 8 01 10 12 02 B0 04 30 75 Until complete 0x302/0x303 M0 COMPLETE/OK near1200 normalized and about58 Hall Seat moves from front endpoint to about25.8 mm Target error <=max(100 raw; two Hall pulses)
67 MEM-001 Save after absolute move P0 All axes calibrated; record 0x302/0x303 before move 0x300 8 01 10 20 03 88 13 40 1F After all motors settle 0x302/0x303 M0 position near5000; Hall near240; MaxHall near480 M0 near107.5 mm Position saved only after coast/motion is fully stopped
68 MEM-002 Restore last stopped position P0 MEM-001 passed and power removed only after completion Power cycle N/A N/A 5 s 0x301/0x302/0x303 PositionValidMask=0F; M0 position/Hall equal pre-power values No motor automatically moves Hall difference ≤1 and normalized difference ≤2 Hall equivalents
69 MEM-003 Save after timed stop P0 All axes calibrated 0x300 8 03 17 21 03 F4 01 F4 01 After completion then power cycle 0x302/0x303 M3 Hall/position restore to the value captured at stop Legrest remains still Restored values equal final pre-power feedback within1 Hall
70 MEM-004 Power loss during movement P0 All axes calibrated and M0 moving Remove power while active N/A N/A Power cycle 0x301/0x302/0x303 M0 PositionValid=0; normalized position=FFFF No automatic movement Old pre-motion position must not be restored; then HOM-002 restores the origin Destructive power-cycle test
71 MEM-005 Dual-slot CRC fallback P1 Create newest corrupt slot and older valid clean SMV8 slot Power cycle N/A N/A 5 s 0x301/0x302/0x303 Older valid clean state restored No automatic movement Position and MaxHall match valid slot Bench flash fixture
72 FBK-001 Feedback periods P0 System idle N/A N/A N/A 2 s 0x301/0x302/0x303 All MotorID 0-3 present and Alive continuous No movement 301=100±20 ms; per-axis302=100±20 ms;303=500±50 ms
73 FLT-001 Hall stall isolation P1 Two axes active; safely block target axis N/A N/A N/A Until fault 0x301/0x302/0x303 Only target FaultedMask bit; STALL result/flag Target output off; other axis follows policy No wrong-axis stop or fault bit Use safe low current
74 FLT-002 CAN bus-off P0 At least one output active Disconnect/force bus-off N/A N/A 20 ms N/A Bus-off counter/fault latched All outputs low Safe-off within20 ms
75 FLT-003 Heater ADC read fault P1 Heater active; inject ADC read errors N/A N/A N/A 50 ms 0x4FA Heater feedback=0 P16.7 low Three consecutive 10 ms sample errors stop heat
76 FLT-004 Initialization failure P0 Force selected peripheral init failure then reset N/A N/A N/A 100 ms N/A No active feedback All motor/fan/heater outputs low No transient start
@@ -0,0 +1,177 @@
# 座椅控制器 CAN/HIL 测试规范
版本:2.3(2026-07-30)
对象:`Seat_Controller_demo`、`量程测试脚本`
MCU:CYT4BFBCJE / CY_MCU_rev_d / Cortex-M0+ Flash
总线:Classic CAN,11-bit 标准帧,500 kbit/s,DLC=8,Intel/Little-endian
## 1. 目的与范围
本规范用于验证统一控制器的座椅通风、靠背加热、四轴移动、自动标定、反馈周期和安全停机。测试分为静态构建、无机械动作 CAN 检查、单轴低风险动作、多轴并发、全行程标定和故障注入。未经安全确认不得直接跳到多轴或标定阶段。
配套文件:
- 移动矩阵:`Seat_Controller_demo/Docs/outputs/seat_move_four_motor_20260724/座椅四电机移动功能CAN矩阵表.xlsx`
- 可执行向量:`Seat_Controller_demo/Docs/座椅控制器_CAN_测试向量.csv`
- 控制器镜像:`Seat_Controller_demo/IAR/rev_d/Exe/Seat_Controller_demo.hex`
- 量程程序:`量程测试脚本/tests/03_full_range_test`
- 功能模式源表:仓库根目录 `座椅功能模式配置表.xlsx`
- 当前实物操作说明:`Seat_Controller_demo/Tests/manual_hil/README_CN.md`
## 1.1 标定基线与绝对坐标
所有轴统一定义“最近端=0 Hall=0%=绝对位置0”,“从最近端向最远端=TowardFar=Hall递增”,“最远端=MaxHallCount=100%”。实测基线如下。
| MotorID | 功能 | 最近端(0%) | 最远端(100%) | MaxHall | 全量程 | 单 Hall 分辨率 |
|---:|---|---|---|---:|---|---|
| 0 | 水平前后 | 座椅最前方 | 座椅最后方 | 480 | 215 mm | 0.4479 mm |
| 1 | 坐盆斜上下 | 坐盆最上方 | 坐盆斜向下方 | 374 | 水平51 mm、垂直56 mm | 水平0.1364 mm、垂直0.1497 mm |
| 2 | 靠背前后 | 最前方,靠背垂直地面 | 向后躺倒 | 917 | 68° | 0.0742° |
| 3 | 腿托上下 | 最下方,腿托垂直地面 | 向上抬起 | 647 | 72° | 0.1113° |
绝对换算统一使用 `round(HallCount × FullScale / MaxHallCount)`;归一化位置使用 `round(HallCount × 10000 / MaxHallCount)`。M1 对同一归一化位置分别换算水平与垂直分量。CAN MotorID 与 DRV8718 物理 Mode 为同号映射。`PositionValidMask`表示ECU中的绝对位置记录是否可信,不表示轴位于最近端。首次刷写 SMV8、两个槽均无效或运动中掉电后,当前 Hall 原点不可信;本台架量程已确认,可先对失效轴执行 `HOME_KNOWN_RANGE(0x0B)` 回最近端建立 Hall=0,再接受绝对/相对位置命令,不需要重新访问最远端。若机构在ECU掉电时被外力改变或位置可信性未知,不能直接把当前位置赋为绝对位置,必须无条件对相关轴真实执行 `0x0B`;测试界面的强制归零操作会依次处理全部四轴。
## 2. 强制安全条件
1. 座椅必须固定,四轴全行程无人员、工具、线束和硬限位外障碍;12 V 电源具有限流和急停。
2. 第一次动作只允许单轴 300~500 ms、30%~50% 等级,并安排人员观察真实方向。
3. 必须确认同号轴映射:M0 水平→physical0,M1 坐盆→physical1,M2 靠背→physical2,M3 腿托→physical3。任一 MotorID 驱动了错误执行机构时立即停止,不得继续位置标定。
4. 运行量程测试脚本或发送全行程标定命令前,先准备可随时发送的 `0x300 09 10 00 00 00 00 00 00`,并保留硬件急停。
5. 加热测试需监控供电电流与表面温升;当前 ADC 电流值仅用于台架可见性,没有量产过流阈值标定,不能替代外部限流。
## 3. 信号速查
### 3.1 热管理
- RX `0x29E`:Byte0[1:0] 风扇开关,Byte0[3:2] 风扇模式,Byte0[5:4] 加热开关;Byte2[3:0] 风扇档位,Byte2[7:4] 加热档位。
- RX `0x2AA`:Byte0=1;Byte2[2:0] 风扇最大配置,Byte2[5:3] 加热最大配置。
- RX `0x3CE`:Byte0[5:4] 加热模式,1/2 有效。
- RX `0x42A`:Byte0[1:0] 风扇开关,Byte0[3:2] 加热开关。
- TX `0x4FA`:Byte0[2:0] 风扇反馈,Byte0[5:3] 加热反馈;0=故障/无效,1=关闭,2/3/4=1/2/3档。
风扇为 100 Hz;首次启动 100% 持续 2 s,随后 1/2/3档分别为 50%/70%/100%。靠背加热为 100 Hz,1/2/3档分别为 30%/60%/90%,连续加热 600 s 后安全关闭。
### 3.2 移动命令 `0x300`
Byte0=Opcode;Byte1=`0x10 | MotorID | (Direction<<2)`;Byte2..3=Sequence;Byte4..5=ParamA;Byte6..7=ParamB,所有多字节字段低字节在前。Direction:0=停止/不适用,1=TowardFar,2=TowardNear。
| Opcode | 命令 | ParamA | ParamB |
|---:|---|---|---|
| `01` | 绝对位置 | 0~10000 | 0=轴默认超时,或100~60000 ms |
| `02` | 相对位置 | int16,-10000~10000 | 0=轴默认超时,或100~60000 ms |
| `03` | 定时移动 | 100~60000 ms | 驱动等级×10,300~1000 |
| `08` | 单轴停止 | 0 | 0 |
| `09` | 全部停止 | 0 | 0 |
| `0A` | 全行程标定 | `0xA55A` | 每个寻端行程10~600 s |
| `0B` | 已知量程回最近端 | `0x4B52` | 最近端寻找10~600 s |
| `0C` | 查询状态 | 0 | 0 |
| `0D` | 清故障 | `0xC33C` | 0 |
### 3.3 移动反馈
- `0x301`:Byte0 低半字节 ACK、高半字节 SystemState;Byte1 RejectReason;Byte2..3 Sequence;Byte4 Opcode;Byte5 ActiveMask/抑制/标定/故障;Byte6 FaultedMask/PositionValidMask;Byte7 低半字节 Alive、高半字节 FirmwareRevision(当前为 2)。
- `0x302`:Byte0 MotorID;Byte1 TaskState/Result;Byte2 方向、Active、PositionValid、端点、堵转和驱动故障;Byte3..4 Sequence;Byte5..6 归一化位置;Byte7 驱动等级。
- `0x303`:Byte0 MotorID;Byte1 LastEvent/FaultFlags;Byte2..3 HallCount;Byte4..5 MaxHallCount;Byte6 Hall速度(0.5 pulse/s/LSB);Byte7 Retry/Alive。
ACK:0 NONE、1 ACCEPTED、2 REJECTED、3 DUPLICATE、4 STOP_ALL_ACCEPTED。RejectReason 与 TaskState/Result 以移动矩阵“枚举定义”页为准。`0x301` Byte5 bit5 当前固定为0,预留给未来通信监控。
RejectReason:0 NONE、1 BAD_DLC、2 BAD_VERSION、3 BAD_OPCODE、4 BAD_MOTOR、5 BAD_DIRECTION、6 PARAM_RANGE、7 POSITION_INVALID、8 SEQUENCE_OLD、9 BUSY_SAME_MOTOR、10 INTERLOCK、11 FAULT_ACTIVE、12 SERVICE_MODE_REQUIRED、13 CAL_DATA_INVALID、14 INTERNAL_ERROR。
TaskState:0 IDLE、1 ACCEPTED、2 POSITION_RUN、3 TIMED_RUN、4 CALIBRATION、5 STOPPING、6 COMPLETE、7 FAULT。Result:0 NONE、1 RUNNING、2 OK、3 STOPPED_BY_CMD、4 ENDPOINT_REACHED、5 STALL、6 TIMEOUT、7 DRIVER_FAULT、8 HALL_FAULT、9 INVALID_CMD、10 INTERLOCKED、11 CALIBRATION_FAILED。
### 3.4 本地位置记忆(SMV8)
- 两个128-byte Work Flash 小扇区交替保存,记录含 Magic/Version、单调 Sequence、Clean标志、四轴绝对 Hall、MaxHall、最大速度、累计 Hall 上升沿、物理绝对量、PositionValid 和 CRC32。
- 每个动作命令真正启动前先写入较新 dirty 记录;写完才允许 PWM 启动。若此时掉电,新记录阻止旧 clean 位置被恢复。
- 所有应用队列为空、四轴任务停止且底层惯性计数结束后,才写入较新 clean 记录。多轴并发只在最后一轴停稳后统一保存,避免运行中阻塞 Flash 操作。
- 正常重启恢复 Hall/MaxHall/绝对量后,`0x302` 直接报告归一化绝对位置,`0x303` 报告 Hall/MaxHall;不自动移动座椅。
- dirty、CRC错误、版本不匹配或物理绝对量与 Hall 换算不一致时不恢复位置,PositionValid=0,绝对/相对位置命令拒绝。量程已确认时用 `0x0B` 只寻找最近端:成功后Hall=0、PositionValid=1、MaxHall保持480/374/917/647,并在停稳后写入clean SMV8。
- `PositionValidMask=0xF`且四轴处于非零位置是正常状态;它说明SMV8保存的是四轴当前绝对位置。只有`0x302 Near=1`且`0x303 Hall≈0`才能证明对应轴位于最近端。强制归零的测试报告必须列出每轴真实发送的`0x0B`、终态Near、Hall和Position,禁止仅修改/观察Mask后宣称已归零。
- 已知量程归零使用90% Duty。端点活动窗口统计Hall双边沿,只有完整500 ms窗口内零边沿才能判定端点;绝对位置和量程仍按上升沿计数。第一次寻近端若累计不到4个原始Hall边沿,不允许直接把Hall清零;固件必须先向远端取得至少4个真实双边沿,再重新寻近端,回程边沿数应与离端边沿数相符(允许1个采样边界误差)。第二次仍无足够Hall或验证方向无法运动时结果必须为CALIBRATION_FAILED、PositionValid=0。已在最近端的轴因此会短暂离端再返回。
- 正常位置命令在安全事件停止时,若最终Hall已进入目标±2脉冲容差,则结果按OK完成;底层LastEvent仍保留在`0x303`供诊断。超过2脉冲的STALL/TIMEOUT不得由上位机放宽或伪判通过。
## 4. 仪器与记录
- IAR EWARM 9.2、CMSIS-DAP 烧录器。
- ZCANPRO + USBCANFD-200U,通道配置为 500 kbit/s Classic CAN,终端电阻按台架拓扑配置。
- 双通道以上示波器/逻辑分析仪:至少观察目标 PWM 和方向/使能;多轴干涉测试建议同时观察四路。
- 12 V 可限流电源、电流钳;加热测试另需温度计。
- 每个用例记录:发送时间、完整 TX/RX 帧、机械方向、Hall 前后值、PWM 频率/占空比、停止延迟、结果和异常截图。
若当前没有示波器,仍可执行CAN控制/反馈、Hall/绝对位置、实际机械方向、风量、温升、正常掉电记忆和协议拒绝测试;PWM频率/占空比、换向死区及硬实时关断延迟必须明确记为 `BLOCKED(缺少示波器)`,不得依据CAN反馈推定PASS。
## 5. 执行顺序
1. 全量 IAR 构建,确认 0 error / 0 warning 和目标宏。
2. 上电安全与周期反馈,不发送任何动作命令。
3. 通风与加热独立/组合测试。
4. 移动协议非法帧、查询、重复 Sequence、全停测试。
5. 每轴 300~500 ms 正反向点动并目视签字确认映射。
6. 单轴定时、同轴换向、单轴停止和四轴并发互不干涉。
7. 已知量程回最近端、每次移动结束保存、正常掉电恢复、运动中掉电失效及恢复、绝对/相对位置精度。
8. 堵转、驱动故障、CAN bus-off、C-SPY 结束和看门狗路径。
每个阶段开始前先发送全停,结束后再次发送全停。任何实物动作与预期不符时停止后续用例。
### 5.1 当前台架回归路线(已确认量程、无示波器)
本轮不重复全轴标定和25/50/75%定位。执行顺序为:
1. `04_can_full_function.py`:热管理档位/组合CAN反馈,以及每轴从当前位置偏移约12%再返回的实物冒烟。
2. `05_seat_function_bridge.py` + `run_seat_function_gui.bat`:从根目录配置表连续动态读取当前11种模式并展开为12个场景;小憩模式只保留1个标准场景,新增“小腿解压悬浮模式”,轻松进出模式包含2个场景。执行前显示预期物理动作、归一化位置、重算Hall和原表Hall,结束后保存CAN自动结果与人工实物观察。
3. `02_power_memory.py` 默认 `PHASE="CLEAN"`:只验证正常掉电SMV8恢复。
4. `03_protection_faults.py` 默认 `PHASE="AUTO"`:验证协议拒绝和Sequence保护。
运动中掉电会使M0位置有效性按设计失效。把 `02_power_memory.py` 改为 `PHASE="MOVING_LOSS"` 后,脚本先验证旧位置未被错误恢复,再默认用 `0x0B` 将M0回最近端恢复。旧 `01_calibration_position.py` 和 `04_waveform_full_function.py` 已设为归档保护,默认只记录SKIP。
配置表CAN目标以物理值按当前量程换算,不直接使用其Hall文本。办公模式M0原表已修正为约346 Hall;当前剩余显著冲突为女王副驾(行驶)M3 22°应为3056/约198 Hall而非178 Hall。
## 6. 通过标准
### 6.1 通用与周期
- 上电初始化和错误路径中所有执行器输出为低;P11.2 始终为 GPIO 低。
- `0x4FA`、`0x301` 周期 100 ms ±20 ms。
- 每个 MotorID 的 `0x302` 周期 100 ms ±20 ms,轴间约 25 ms;每个 MotorID 的 `0x303` 周期 500 ms ±50 ms,轴间约 125 ms。
- Alive 按模 16连续递增;2 s 监听窗口内 M0~M3 均不得漏报。
- PWM 频率 100 Hz ±1%;稳态占空比为目标值 ±2%。
### 6.2 通风与加热
- 风扇冷启动 100% 持续 2.00 s ±50 ms,之后切换目标档;已运行时换档不得重复 2 s 启动段。
- 风扇/加热档位反馈与实际限幅后物理档一致;关闭反馈为1,无效/故障反馈为0。
- 加热连续运行 600 s ±1 s 后输出关闭,状态为 TIMEOUT_SAFE_OFF,`0x4FA` 加热反馈为0。
- ADC 连续3次读取错误后加热关闭;不得把未标定电流值当作量产过流判据。
### 6.3 移动与干涉消除
- 定时动作持续时间误差不大于 ±50 ms 或命令时间的 ±2%(取较大者)。
- TowardFar Hall 单调增加,TowardNear Hall 单调减少;M0分别后移/前移,M1分别斜向下/斜向上,M2分别后躺/回正,M3分别抬起/放下。单轴动作不得改变其他轴 Sequence、TaskState 或 Hall(允许机械耦合造成的可解释微小惯性,但不得有驱动输出)。
- 同轴换向时先观察到方向/PWM 全关,死区不小于100 ms,再使能反向;正反桥臂不得重叠。
- `STOP_MOTOR` 只停止目标轴,软件处理延迟不大于10 ms;`STOP_ALL` 从完整 CAN 帧接收到所有 PWM kill 不大于2 ms,`ActiveMask=0` 反馈不大于20 ms。
- 重复帧只返回 DUPLICATE,不产生第二次动作;旧 Sequence 拒绝且输出不变;旧 Sequence 的 STOP_ALL 仍必须执行。
- 未标定时绝对/相对位置命令拒绝,CurrentPosition=`0xFFFF`;定时命令仍可用。
### 6.4 标定与位置
- 每轴按负端→正端完成,标定中 `0x301 SystemState=CALIBRATION`、Byte5 bit6=1,`0x302 TaskState=CALIBRATION`,周期反馈不中断。
- MaxHallCount 软件允许范围(实测值的70%~130%):M0 336~624,M1 261~486,M2 641~1192,M3 452~841,且大于等于8。台架目标值分别为480、374、917、647;超出允许范围标定失败。
- 成功后对应 PositionValid=1,负端位置0±1%,正端10000±1%。绝对位置目标误差不大于 `max(100 raw,2个Hall脉冲换算值)`。
- 每个移动会话开始前记录变 dirty,所有轴停稳后写入 clean SMV8;断电重启后 Hall、MaxHall、归一化位置和 PositionValid 恢复。一个有效 SMV8 槽加一个旧格式/损坏槽时仍使用有效槽。SMV5/SMV6 使用旧量程尺度,SMV7 使用旧非同号通道映射;升级后均不能直接恢复旧绝对位置,应对失效轴执行 `0x0B` 回最近端。
- `0x0B` 成功后只更新当前绝对原点,不重测量程;对应 `0x302` 必须报告 COMPLETE/OK、Near=1、PositionValid=1、位置0±1%,`0x303` Hall≤2且MaxHall仍为确认值。若初始Hall>2,上位机日志还必须观察到至少一个0与初始值之间的中间Hall,否则按FAIL处理,防止旧固件直接跳零。
- 标定期间发送 STOP_ALL 必须在2 ms内停机,不得提交未完成的新量程;允许保存“旧MaxHall+PositionValid=0”的安全状态,任务反馈为停止/标定失败,不能错误报告 OK。
- 正常停止后掉电,重启 Hall 差值不大于1;归一化位置差值不大于2个 Hall 对应值。运动中直接掉电,重启后该位置必须无效,禁止恢复为运动前的旧有效位置。
## 7. 测试结果分级
- PASS:CAN、物理动作、时序和安全判据全部满足。
- FAIL:任何输出误动作、映射错误、停止超时、反馈字段错误、周期丢失或持久化错误。
- BLOCKED:硬件未连接、无法安全完成全行程或缺少必要测量仪器;不得按 PASS 处理。
- NOT RUN:尚未执行。
## 8. 当前必须人工确认的项目
- MotorID 0/1/2/3 是否分别只驱动水平、坐盆、靠背、腿托机构。
- 四轴正/负方向是否与新矩阵 0%/100% 端定义一致。
- Work Flash 两个连续128-byte小扇区是否已在整车软件内存规划中保留,禁止与其它标定/NVM数据复用。
- 0x300~0x303 在整车网络中的 ID 是否已由网络负责人批准,避免与现网报文冲突。