feat: add VCU status feedback and map DEBUG safety inputs

This commit is contained in:
shenlefengnai-rgb
2026-10-03 21:52:14 +08:00
parent dbbda5624a
commit 1e7b2e7f96
305 changed files with 32906 additions and 68 deletions
+2
View File
@@ -3,6 +3,7 @@
/* CAN1 is 500 kbit/s; CAN2 is 250 kbit/s. Both share IDs and parsing. */
#define AMI_CAN_ID_DRIVETRAIN_DISPLAY 0x182U
#define AMI_CAN_ID_VCU_STATUS 0x183U
#define AMI_CAN_ID_SDC_HEARTBEAT 0x6A0U
#define AMI_CAN_ID_SDC_ADC 0x6A1U
#define AMI_CAN_ID_SDC_FREQUENCY 0x6A2U
@@ -19,6 +20,7 @@
#define AMI_ADC_TIMEOUT_MS 1500U
#define AMI_BMS_TIMEOUT_MS 2000U
#define AMI_DRIVETRAIN_TIMEOUT_MS 2000U
#define AMI_VCU_STATUS_TIMEOUT_MS 500U
#define AMI_SCREEN_BOOT_DELAY_MS 800U
#define AMI_SCREEN_RESYNC_MS 5000U
+92 -14
View File
@@ -42,6 +42,11 @@ static volatile uint32_t adc_last_rx_ms;
static volatile uint32_t bms_status_last_rx_ms;
static volatile uint32_t bms_temperature_last_rx_ms;
static volatile uint32_t drivetrain_last_rx_ms;
static volatile uint32_t vcu_status_last_rx_ms;
/* Task 7 = unknown; lock/EBS 0 = unknown, 1 = green, 2 = red. */
static volatile uint8_t vcu_display_state[3] = {7U, 0U, 0U};
static volatile uint8_t vcu_display_dirty;
static volatile bool vcu_status_received;
/* Preserve the exact source units even where the HMI has integer controls. */
static volatile uint16_t sdc_adc_mv;
static volatile uint16_t sdc_frequency_hz[4];
@@ -246,12 +251,59 @@ static void SetDashValue(uint32_t index, int32_t value)
SetDashValid(index, true);
}
static void UpdateDebugFromSdcGpio(uint16_t levels)
{
/* PD order supplied by the wiring table; HMI dN order is different. */
static const uint8_t debug_index[15] = {
0U, 5U, 2U, 6U, 7U, 1U, 3U, 4U,
8U, 9U, 10U, 11U, 12U, 13U, 14U
};
for (uint32_t bit = 0U; bit < 15U; ++bit)
{
uint32_t index = debug_index[bit];
/* All fifteen inputs: low = normal, high = fault. */
uint8_t state = (levels & (1U << bit)) ? 2U : 1U;
if (display_state.debug_state[index] != state)
{
display_state.debug_state[index] = state;
display_dirty |= DIRTY_DEBUG(index);
}
}
/* d15 (12V_Control) has no source in the fifteen-input table. */
}
static bool DecodeCan(uint32_t identifier, uint32_t id_type,
uint32_t length, const uint8_t *data)
{
uint32_t now = HAL_GetTick();
if (id_type == FDCAN_STANDARD_ID)
{
if (identifier == AMI_CAN_ID_VCU_STATUS)
{
/* Custom VCU feedback; requests never update these actual states. */
if ((length != 8U) || (data[7] != 1U) || (data[0] > 6U) ||
(data[1] > 1U) || (data[2] > 1U) || ((data[3] & 0xF8U) != 0U) ||
(data[5] != 0U) || (data[6] != 0U))
{
return false;
}
uint8_t states[3] = {
(data[3] & 1U) ? data[0] : 7U,
(data[3] & 2U) ? (data[1] ? 1U : 2U) : 0U,
(data[3] & 4U) ? (data[2] ? 1U : 2U) : 0U
};
for (uint32_t i = 0U; i < 3U; ++i)
{
if (vcu_display_state[i] != states[i])
{
vcu_display_state[i] = states[i];
vcu_display_dirty |= (uint8_t)(1U << i);
}
}
vcu_status_last_rx_ms = now;
vcu_status_received = true;
return true;
}
if (identifier == AMI_CAN_ID_DRIVETRAIN_DISPLAY)
{
/* Version 1 display input only; no motor actuation is performed. */
@@ -305,7 +357,7 @@ static bool DecodeCan(uint32_t identifier, uint32_t id_type,
((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24);
sdc_gpio_received = true;
debug_last_rx_ms = now;
/* No PD0..PD14 -> named safety signals / normal polarity mapping. */
UpdateDebugFromSdcGpio(sdc_gpio_state);
return true;
}
if ((identifier == AMI_CAN_ID_SDC_DAC_ACK) && (length == 4U))
@@ -379,7 +431,9 @@ static void ConfigureCanPort(FDCAN_HandleTypeDef *hfdcan)
if (HAL_FDCAN_ConfigFilter(hfdcan, &filter) != HAL_OK) { Error_Handler(); }
filter.FilterIndex = 3U;
filter.FilterType = FDCAN_FILTER_DUAL;
filter.FilterID1 = AMI_CAN_ID_DRIVETRAIN_DISPLAY;
filter.FilterID2 = AMI_CAN_ID_VCU_STATUS;
if (HAL_FDCAN_ConfigFilter(hfdcan, &filter) != HAL_OK) { Error_Handler(); }
filter.IdType = FDCAN_EXTENDED_ID;
@@ -507,6 +561,15 @@ static void UpdateTimeoutsAndLeds(void)
__disable_irq();
/* Sample time after excluding RX IRQs: a newer timestamp must not underflow. */
now = HAL_GetTick();
if (vcu_status_received &&
((now - vcu_status_last_rx_ms) > AMI_VCU_STATUS_TIMEOUT_MS))
{
vcu_display_state[0] = 7U;
vcu_display_state[1] = 0U;
vcu_display_state[2] = 0U;
vcu_display_dirty |= 7U;
vcu_status_received = false;
}
if ((adc_last_rx_ms == 0U) || ((now - adc_last_rx_ms) > AMI_ADC_TIMEOUT_MS))
{
SetDashValid(7U, false);
@@ -528,28 +591,31 @@ static void UpdateTimeoutsAndLeds(void)
SetDashValid(4U, false);
SetDashValid(5U, false);
}
__enable_irq();
/* Recheck within the RX critical section to avoid erasing a new snapshot. */
debug_timeout = (debug_last_rx_ms == 0U) ||
((now - debug_last_rx_ms) > AMI_CAN_DATA_TIMEOUT_MS);
if (debug_timeout && !debug_timeout_applied)
{
uint32_t i;
__disable_irq();
for (i = 0U; i < 16U; ++i)
{
display_state.debug_state[i] = 0U;
}
display_dirty |= DIRTY_DEBUG_ALL;
__enable_irq();
debug_timeout_applied = true;
}
else if (!debug_timeout)
{
debug_timeout_applied = false;
}
__enable_irq();
}
static void SendNextDirtyValue(void)
{
static const char *const vcu_prefix[3] = {
"current_task=", "lock_state=", "ebs_state="
};
static const char *const dash_prefix[8] = {
"dash.speed.val=", "dash.ts.val=", "dash.mct.val=", "dash.dl.val=",
"dash.djwd.val=", "dash.mcuwd.val=", "dash.dtwd.val=", "dash.lvbat.val="
@@ -560,6 +626,7 @@ static void SendNextDirtyValue(void)
};
static uint32_t cursor;
uint32_t dirty;
uint8_t vcu_dirty;
uint32_t bit;
uint32_t index;
int32_t value;
@@ -573,23 +640,31 @@ static void SendNextDirtyValue(void)
__disable_irq();
dirty = display_dirty;
vcu_dirty = vcu_display_dirty;
__enable_irq();
if (dirty == 0U)
if ((dirty == 0U) && (vcu_dirty == 0U))
{
return;
}
/* Fair rotation prevents rapid source updates from starving other fields. */
for (uint32_t offset = 0U; offset < 32U; ++offset)
for (uint32_t offset = 0U; offset < 35U; ++offset)
{
index = (cursor + offset) & 31U;
if ((dirty & (1UL << index)) != 0U) { break; }
index = (cursor + offset) % 35U;
if ((index < 32U) ? ((dirty & (1UL << index)) != 0U)
: ((vcu_dirty & (1U << (index - 32U))) != 0U)) { break; }
}
bit = 1UL << index;
cursor = (index + 1U) & 31U;
bit = 1UL << ((index < 32U) ? index : index - 32U);
cursor = (index + 1U) % 35U;
__disable_irq();
if (index < 8U)
if (index >= 32U)
{
value = vcu_display_state[index - 32U];
prefix = vcu_prefix[index - 32U];
vcu_display_dirty &= (uint8_t)~bit;
}
else if (index < 8U)
{
value = display_state.dash_value[index];
prefix = dash_prefix[index];
@@ -617,13 +692,14 @@ static void SendNextDirtyValue(void)
prefix = debug_prefix;
}
}
display_dirty &= ~bit;
if (index < 32U) { display_dirty &= ~bit; }
__enable_irq();
if (!StartScreenAssignment(prefix, value))
{
__disable_irq();
display_dirty |= bit;
if (index < 32U) { display_dirty |= bit; }
else { vcu_display_dirty |= (uint8_t)bit; }
__enable_irq();
}
}
@@ -633,6 +709,7 @@ void AMI_App_Init(void)
HAL_GPIO_WritePin(STATUS_LED1_GPIO_Port, STATUS_LED1_Pin, LedInactiveLevel());
HAL_GPIO_WritePin(STATUS_LED2_GPIO_Port, STATUS_LED2_Pin, AMI_LED_ACTIVE_LEVEL);
display_dirty = DIRTY_DASH_ALL | DIRTY_DEBUG_ALL | DIRTY_COLOR_ALL;
vcu_display_dirty = 7U;
ConfigureCan();
RestartScreenRx();
}
@@ -649,6 +726,7 @@ void AMI_App_Service(void)
{
__disable_irq();
display_dirty |= DIRTY_DASH_ALL | DIRTY_DEBUG_ALL | DIRTY_COLOR_ALL;
vcu_display_dirty |= 7U;
__enable_irq();
last_screen_sync_ms = now;
}
+3 -1
View File
@@ -23,6 +23,8 @@ CAN1 为 500 kbit/s、CAN2 为 250 kbit/s。FDCAN 时钟为 64 MHz,两路 Cloc
- FDCAN1、FDCAN2 使用相同的四条标准过滤器和一条扩展过滤器,接收自定义显示 0x182、SDC 0x6A0..0x6A6 与 BMS 0x140 / 0x156 / 0x186040F3 / 0x186140F3。
- 标准 0x182 / DLC 8:小端 int16 扭矩千分、0.1℃ 电机温度、0.1℃ MCU 温度;有效标志及版本校验见 [DBC 说明](../docs/AMI_CAN_DBC说明.md)。设置 dash.mct / djwd / mcuwd,独立超时 2000 ms。
- v2.4 标准 0x183 / DLC 8:VCU 实际任务、锁定、EBS 显示反馈,各有有效标志,独立超时 500 ms;错误帧不刷新超时。过滤器 3 使用双 ID 接收 0x182/0x183,两路共用解码。UART 更新 current_task/lock_state/ebs_state,任务请求不改写实际状态;屏幕接口需配套 1.4。
- 标准 0x6A6 的 PD0~PD14 按用户表顺序映射为 d0,d5,d2,d6,d7,d1,d3,d4,d8..d14;全部低电平正常、高电平故障,d15 保持未知。有效心跳维持已有状态;SDC 心跳/GPIO 均超过 500 ms 未接收后置未知,恢复需新 GPIO 采样。
- SDC ADC 为低压电池电压;BMS 总压、偏置电流和最高有效实际温度按源协议解码。旧 0x180 / 0x181 / 0x280 不再接收。
- 主循环通过 USART2 TX DMA 一次发送一条完整 TJC 指令,末尾为三个真实字节 `FF FF FF`。
- 仅变化后的数值排队,字段轮转和 DMA 合并最新值,避免刷新饥饿;白色为有效数值,灰色为未知/失效,ADC 和 BMS 各自计时。
@@ -58,4 +60,4 @@ CubeMX 配置源为 `AMI_Controller.ioc`。重新生成时保留 USER CODE 区
4. 下载 `AMI.HMI` 后逐条验证仪表数值、16 路 debug 状态及 6 个任务按钮。
5. 2026-10-03 已完成 v2.1 CAN1 500K 与 CAN2 250K 分别到 MCU / USART2 / COM4 / 官方 HMI 调试器的联调;物理屏幕、真实节点、两路同时接入和 LED 极性尚未实测。见 [双 CAN 测试报告](../docs/CAN1_CAN2_v2.1测试报告.md)。
当前 v2.2 BIN 已烧录并完整回读比对;新 0x182 在 CAN2 250K 实测,见 [显示 CAN 测试报告](../docs/AMI自定义显示CAN测试报告.md)。v2.1 的双 CAN 分别联调和两路均为 500K 的 v2.0 历史证据及 BIN 保留;SDC 仍为 500K。
当前 v2.4 BIN 已烧录并完整回读 16428 字节一致,RAM 2640 字节,详见 [VCU 状态反馈报告](../docs/VCU状态反馈测试报告.md)。此前 v2.3 BIN 已归档,十五路映射历史实测见 [映射报告](../docs/DEBUG十五路CAN映射测试报告.md)。v2.2 的 0x182 CAN2 250K 历史实测见 [显示 CAN 测试报告](../docs/AMI自定义显示CAN测试报告.md)。v2.1 双 CAN 分别联调及 v2.0 历史证据保留;SDC 仍为 500K。