feat: update CAN integration and T2G seat firmware
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
+111
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,,,,
|
||||
|
Binary file not shown.
@@ -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 是否已由网络负责人批准,避免与现网报文冲突。
|
||||
Reference in New Issue
Block a user