fix: recover CAN bus-off and expire stalled transmissions
This commit is contained in:
@@ -65,6 +65,7 @@ typedef struct
|
|||||||
#define ADC_REPORT_PERIOD_MS 500U
|
#define ADC_REPORT_PERIOD_MS 500U
|
||||||
#define PWM_REPORT_PERIOD_MS 20U
|
#define PWM_REPORT_PERIOD_MS 20U
|
||||||
#define GPIO_REPORT_PERIOD_MS 1000U
|
#define GPIO_REPORT_PERIOD_MS 1000U
|
||||||
|
#define CAN_TX_TIMEOUT_MS 100U
|
||||||
#define CAN_HEARTBEAT_PERIOD_MS 100U
|
#define CAN_HEARTBEAT_PERIOD_MS 100U
|
||||||
#define DAC_CAN_TIMEOUT_MS 100U
|
#define DAC_CAN_TIMEOUT_MS 100U
|
||||||
#define PWM_TIMEOUT_MS 1000U
|
#define PWM_TIMEOUT_MS 1000U
|
||||||
@@ -129,6 +130,26 @@ static char uart_line[UART_LINE_SIZE];
|
|||||||
static uint8_t uart_line_len;
|
static uint8_t uart_line_len;
|
||||||
static uint8_t assi_lamp_state;
|
static uint8_t assi_lamp_state;
|
||||||
|
|
||||||
|
/* Read through SWD; counters are observations, not a bus capture. */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t last_esr;
|
||||||
|
uint32_t bus_off_events;
|
||||||
|
uint32_t recovery_events;
|
||||||
|
uint32_t passive_events;
|
||||||
|
uint32_t tx_queued;
|
||||||
|
uint32_t tx_completed;
|
||||||
|
uint32_t tx_failed;
|
||||||
|
uint32_t tx_unobserved;
|
||||||
|
uint32_t tx_dropped;
|
||||||
|
uint32_t abort_requests;
|
||||||
|
uint32_t abort_failures;
|
||||||
|
} CanDiagnostics;
|
||||||
|
static volatile CanDiagnostics can_diagnostics[2];
|
||||||
|
static uint32_t can_tx_started_ms[2][3];
|
||||||
|
static bool can_tx_tracked[2][3];
|
||||||
|
static bool can_tx_abort_requested[2][3];
|
||||||
|
|
||||||
/* USER CODE END PV */
|
/* USER CODE END PV */
|
||||||
|
|
||||||
/* Private function prototypes -----------------------------------------------*/
|
/* Private function prototypes -----------------------------------------------*/
|
||||||
@@ -167,6 +188,7 @@ static uint32_t ReadAdcInputMillivolts(void);
|
|||||||
static uint32_t WheelSpeedMmPerSecond(uint32_t frequency_hz);
|
static uint32_t WheelSpeedMmPerSecond(uint32_t frequency_hz);
|
||||||
static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim);
|
static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim);
|
||||||
static void InitCaptureChannels(void);
|
static void InitCaptureChannels(void);
|
||||||
|
static void ServiceCanHealth(CAN_HandleTypeDef *hcan);
|
||||||
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan);
|
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan);
|
||||||
static void StartCanBus(CAN_HandleTypeDef *hcan, uint32_t filter_bank);
|
static void StartCanBus(CAN_HandleTypeDef *hcan, uint32_t filter_bank);
|
||||||
static void SendCanFrame(CAN_HandleTypeDef *hcan,
|
static void SendCanFrame(CAN_HandleTypeDef *hcan,
|
||||||
@@ -237,6 +259,8 @@ int main(void)
|
|||||||
/* USER CODE END WHILE */
|
/* USER CODE END WHILE */
|
||||||
|
|
||||||
/* USER CODE BEGIN 3 */
|
/* USER CODE BEGIN 3 */
|
||||||
|
ServiceCanHealth(&hcan1);
|
||||||
|
ServiceCanHealth(&hcan2);
|
||||||
PollUart();
|
PollUart();
|
||||||
PollCanBus(&hcan1, "CAN1");
|
PollCanBus(&hcan1, "CAN1");
|
||||||
PollCanBus(&hcan2, "CAN2");
|
PollCanBus(&hcan2, "CAN2");
|
||||||
@@ -366,7 +390,7 @@ static void MX_CAN1_Init(void)
|
|||||||
hcan1.Init.TimeSeg1 = CAN_BS1_1TQ;
|
hcan1.Init.TimeSeg1 = CAN_BS1_1TQ;
|
||||||
hcan1.Init.TimeSeg2 = CAN_BS2_4TQ;
|
hcan1.Init.TimeSeg2 = CAN_BS2_4TQ;
|
||||||
hcan1.Init.TimeTriggeredMode = DISABLE;
|
hcan1.Init.TimeTriggeredMode = DISABLE;
|
||||||
hcan1.Init.AutoBusOff = DISABLE;
|
hcan1.Init.AutoBusOff = ENABLE;
|
||||||
hcan1.Init.AutoWakeUp = DISABLE;
|
hcan1.Init.AutoWakeUp = DISABLE;
|
||||||
hcan1.Init.AutoRetransmission = DISABLE;
|
hcan1.Init.AutoRetransmission = DISABLE;
|
||||||
hcan1.Init.ReceiveFifoLocked = DISABLE;
|
hcan1.Init.ReceiveFifoLocked = DISABLE;
|
||||||
@@ -403,7 +427,7 @@ static void MX_CAN2_Init(void)
|
|||||||
hcan2.Init.TimeSeg1 = CAN_BS1_1TQ;
|
hcan2.Init.TimeSeg1 = CAN_BS1_1TQ;
|
||||||
hcan2.Init.TimeSeg2 = CAN_BS2_4TQ;
|
hcan2.Init.TimeSeg2 = CAN_BS2_4TQ;
|
||||||
hcan2.Init.TimeTriggeredMode = DISABLE;
|
hcan2.Init.TimeTriggeredMode = DISABLE;
|
||||||
hcan2.Init.AutoBusOff = DISABLE;
|
hcan2.Init.AutoBusOff = ENABLE;
|
||||||
hcan2.Init.AutoWakeUp = DISABLE;
|
hcan2.Init.AutoWakeUp = DISABLE;
|
||||||
hcan2.Init.AutoRetransmission = DISABLE;
|
hcan2.Init.AutoRetransmission = DISABLE;
|
||||||
hcan2.Init.ReceiveFifoLocked = DISABLE;
|
hcan2.Init.ReceiveFifoLocked = DISABLE;
|
||||||
@@ -1294,12 +1318,74 @@ static void InitCaptureChannels(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ServiceCanHealth(CAN_HandleTypeDef *hcan)
|
||||||
|
{
|
||||||
|
uint32_t bus = (hcan == &hcan1) ? 0U : 1U;
|
||||||
|
volatile CanDiagnostics *diag = &can_diagnostics[bus];
|
||||||
|
uint32_t now = HAL_GetTick();
|
||||||
|
uint32_t esr = hcan->Instance->ESR;
|
||||||
|
uint32_t previous = diag->last_esr;
|
||||||
|
|
||||||
|
if (((esr & CAN_ESR_BOFF) != 0U) && ((previous & CAN_ESR_BOFF) == 0U))
|
||||||
|
{
|
||||||
|
diag->bus_off_events++;
|
||||||
|
}
|
||||||
|
if (((esr & CAN_ESR_BOFF) == 0U) && ((previous & CAN_ESR_BOFF) != 0U))
|
||||||
|
{
|
||||||
|
diag->recovery_events++;
|
||||||
|
}
|
||||||
|
if (((esr & CAN_ESR_EPVF) != 0U) && ((previous & CAN_ESR_EPVF) == 0U))
|
||||||
|
{
|
||||||
|
diag->passive_events++;
|
||||||
|
}
|
||||||
|
diag->last_esr = esr;
|
||||||
|
|
||||||
|
for (uint32_t index = 0U; index < 3U; index++)
|
||||||
|
{
|
||||||
|
uint32_t mailbox = 1UL << index;
|
||||||
|
if (!can_tx_tracked[bus][index])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (HAL_CAN_IsTxMessagePending(hcan, mailbox) == 0U)
|
||||||
|
{
|
||||||
|
/* TXOK is hardware completion, unlike HAL_OK when enqueuing. */
|
||||||
|
if ((hcan->Instance->TSR & (CAN_TSR_TXOK0 << (8U * index))) != 0U)
|
||||||
|
{
|
||||||
|
diag->tx_completed++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
diag->tx_failed++;
|
||||||
|
}
|
||||||
|
can_tx_tracked[bus][index] = false;
|
||||||
|
can_tx_abort_requested[bus][index] = false;
|
||||||
|
}
|
||||||
|
else if (!can_tx_abort_requested[bus][index] &&
|
||||||
|
(((esr & CAN_ESR_BOFF) != 0U) ||
|
||||||
|
((uint32_t)(now - can_tx_started_ms[bus][index]) >= CAN_TX_TIMEOUT_MS)))
|
||||||
|
{
|
||||||
|
/* Discard stale reports/ACKs; abort is asynchronous and never waits. */
|
||||||
|
if (HAL_CAN_AbortTxRequest(hcan, mailbox) == HAL_OK)
|
||||||
|
{
|
||||||
|
can_tx_abort_requested[bus][index] = true;
|
||||||
|
diag->abort_requests++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
diag->abort_failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan)
|
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan)
|
||||||
{
|
{
|
||||||
hcan->Init.Prescaler = 4U;
|
hcan->Init.Prescaler = 4U;
|
||||||
hcan->Init.SyncJumpWidth = CAN_SJW_1TQ;
|
hcan->Init.SyncJumpWidth = CAN_SJW_1TQ;
|
||||||
hcan->Init.TimeSeg1 = CAN_BS1_5TQ;
|
hcan->Init.TimeSeg1 = CAN_BS1_5TQ;
|
||||||
hcan->Init.TimeSeg2 = CAN_BS2_2TQ;
|
hcan->Init.TimeSeg2 = CAN_BS2_2TQ;
|
||||||
|
hcan->Init.AutoBusOff = ENABLE;
|
||||||
hcan->Init.AutoRetransmission = ENABLE;
|
hcan->Init.AutoRetransmission = ENABLE;
|
||||||
|
|
||||||
if (HAL_CAN_Init(hcan) != HAL_OK)
|
if (HAL_CAN_Init(hcan) != HAL_OK)
|
||||||
@@ -1340,6 +1426,16 @@ static void SendCanFrame(CAN_HandleTypeDef *hcan,
|
|||||||
{
|
{
|
||||||
CAN_TxHeaderTypeDef tx_header = {0};
|
CAN_TxHeaderTypeDef tx_header = {0};
|
||||||
uint32_t mailbox = 0U;
|
uint32_t mailbox = 0U;
|
||||||
|
uint32_t bus = (hcan == &hcan1) ? 0U : 1U;
|
||||||
|
|
||||||
|
/* Account for completion before HAL can reuse a free mailbox. */
|
||||||
|
ServiceCanHealth(hcan);
|
||||||
|
if (((hcan->Instance->ESR & CAN_ESR_BOFF) != 0U) ||
|
||||||
|
(HAL_CAN_GetTxMailboxesFreeLevel(hcan) == 0U))
|
||||||
|
{
|
||||||
|
can_diagnostics[bus].tx_dropped++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tx_header.StdId = std_id;
|
tx_header.StdId = std_id;
|
||||||
tx_header.ExtId = 0U;
|
tx_header.ExtId = 0U;
|
||||||
@@ -1348,7 +1444,27 @@ static void SendCanFrame(CAN_HandleTypeDef *hcan,
|
|||||||
tx_header.DLC = (length <= 8U) ? length : 8U;
|
tx_header.DLC = (length <= 8U) ? length : 8U;
|
||||||
tx_header.TransmitGlobalTime = DISABLE;
|
tx_header.TransmitGlobalTime = DISABLE;
|
||||||
|
|
||||||
(void)HAL_CAN_AddTxMessage(hcan, &tx_header, (uint8_t *)data, &mailbox);
|
if (HAL_CAN_AddTxMessage(hcan, &tx_header, (uint8_t *)data, &mailbox) != HAL_OK)
|
||||||
|
{
|
||||||
|
can_diagnostics[bus].tx_dropped++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (uint32_t index = 0U; index < 3U; index++)
|
||||||
|
{
|
||||||
|
if (mailbox == (1UL << index))
|
||||||
|
{
|
||||||
|
/* A completion can race the HAL mailbox selection. Do not guess TXOK. */
|
||||||
|
if (can_tx_tracked[bus][index])
|
||||||
|
{
|
||||||
|
can_diagnostics[bus].tx_unobserved++;
|
||||||
|
}
|
||||||
|
can_tx_started_ms[bus][index] = HAL_GetTick();
|
||||||
|
can_tx_tracked[bus][index] = true;
|
||||||
|
can_tx_abort_requested[bus][index] = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
can_diagnostics[bus].tx_queued++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BroadcastCanFrame(uint16_t std_id, const uint8_t *data, uint8_t length)
|
static void BroadcastCanFrame(uint16_t std_id, const uint8_t *data, uint8_t length)
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run the actual firmware CAN service/send functions against a host HAL stub."""
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
source = (Path(__file__).resolve().parents[1] /
|
||||||
|
'A11_F407_SDC_ACQ_DAC_CubeMX/Src/main.c').read_text()
|
||||||
|
|
||||||
|
def function(name):
|
||||||
|
start = source.index('static void ' + name + '(', source.index('static void ServiceCanHealth(CAN_HandleTypeDef *hcan)\n{'))
|
||||||
|
brace = source.index('{', start)
|
||||||
|
depth = 1
|
||||||
|
end = brace + 1
|
||||||
|
while depth:
|
||||||
|
depth += (source[end] == '{') - (source[end] == '}')
|
||||||
|
end += 1
|
||||||
|
return source[start:end]
|
||||||
|
|
||||||
|
state_start = source.index('typedef struct\n{\n uint32_t last_esr;')
|
||||||
|
state_end = source.index('static bool can_tx_abort_requested[2][3];', state_start)
|
||||||
|
state_end += len('static bool can_tx_abort_requested[2][3];')
|
||||||
|
harness = r'''
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#define CAN_TX_TIMEOUT_MS 100U
|
||||||
|
#define CAN_ESR_BOFF 4U
|
||||||
|
#define CAN_ESR_EPVF 2U
|
||||||
|
#define CAN_TSR_TXOK0 2U
|
||||||
|
#define CAN_ID_STD 0U
|
||||||
|
#define CAN_RTR_DATA 0U
|
||||||
|
#define DISABLE 0U
|
||||||
|
#define HAL_OK 0U
|
||||||
|
#define HAL_ERROR 1U
|
||||||
|
typedef struct { uint32_t ESR, TSR, pending; } CAN_TypeDef;
|
||||||
|
typedef struct { CAN_TypeDef *Instance; } CAN_HandleTypeDef;
|
||||||
|
typedef struct { uint32_t StdId, ExtId, IDE, RTR, DLC, TransmitGlobalTime; } CAN_TxHeaderTypeDef;
|
||||||
|
static CAN_TypeDef regs[2];
|
||||||
|
static CAN_HandleTypeDef hcan1 = {®s[0]}, hcan2 = {®s[1]};
|
||||||
|
static uint32_t now, abort_calls, add_result, abort_result;
|
||||||
|
static bool race_completion;
|
||||||
|
static uint32_t HAL_GetTick(void) { return now; }
|
||||||
|
static uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *h, uint32_t mask) {
|
||||||
|
return (h->Instance->pending & mask) != 0;
|
||||||
|
}
|
||||||
|
static uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *h) {
|
||||||
|
if (race_completion) { h->Instance->pending = 0; race_completion = false; }
|
||||||
|
return 3 - __builtin_popcount(h->Instance->pending);
|
||||||
|
}
|
||||||
|
static uint32_t HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *h, uint32_t mask) {
|
||||||
|
(void)h; (void)mask; abort_calls++; return abort_result;
|
||||||
|
}
|
||||||
|
static uint32_t HAL_CAN_AddTxMessage(CAN_HandleTypeDef *h, const CAN_TxHeaderTypeDef *hdr,
|
||||||
|
uint8_t *data, uint32_t *mailbox) {
|
||||||
|
(void)hdr; (void)data;
|
||||||
|
if (add_result) return add_result;
|
||||||
|
for (uint32_t i = 0; i < 3; i++) if (!(h->Instance->pending & (1U << i))) {
|
||||||
|
*mailbox = 1U << i; h->Instance->pending |= *mailbox;
|
||||||
|
h->Instance->TSR &= ~(CAN_TSR_TXOK0 << (8 * i)); return HAL_OK;
|
||||||
|
}
|
||||||
|
return HAL_ERROR;
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
harness += source[state_start:state_end] + '\n'
|
||||||
|
harness += function('ServiceCanHealth') + '\n' + function('SendCanFrame')
|
||||||
|
harness += r'''
|
||||||
|
static void reset(void) {
|
||||||
|
memset(regs, 0, sizeof regs);
|
||||||
|
memset((void *)can_diagnostics, 0, sizeof can_diagnostics);
|
||||||
|
memset(can_tx_tracked, 0, sizeof can_tx_tracked);
|
||||||
|
memset(can_tx_abort_requested, 0, sizeof can_tx_abort_requested);
|
||||||
|
now = abort_calls = add_result = abort_result = 0; race_completion = false;
|
||||||
|
}
|
||||||
|
static void send(CAN_HandleTypeDef *h) { uint8_t data[8] = {0}; SendCanFrame(h, 0x6A0, data, 8); }
|
||||||
|
int main(void) {
|
||||||
|
reset(); send(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_queued == 1 && can_diagnostics[0].tx_completed == 0);
|
||||||
|
regs[0].pending = 0; regs[0].TSR = CAN_TSR_TXOK0;
|
||||||
|
ServiceCanHealth(&hcan1); ServiceCanHealth(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_completed == 1); /* counted only once */
|
||||||
|
send(&hcan1); regs[0].pending = 0; ServiceCanHealth(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_failed == 1); /* HAL_OK is not delivery */
|
||||||
|
reset(); send(&hcan2); now = 99; ServiceCanHealth(&hcan2); assert(abort_calls == 0);
|
||||||
|
now = 100; ServiceCanHealth(&hcan2); ServiceCanHealth(&hcan2);
|
||||||
|
assert(abort_calls == 1 && can_diagnostics[1].abort_requests == 1);
|
||||||
|
send(&hcan1); assert(can_diagnostics[0].tx_queued == 1); /* other bus independent */
|
||||||
|
regs[1].pending = 0; ServiceCanHealth(&hcan2);
|
||||||
|
assert(can_diagnostics[1].tx_failed == 1);
|
||||||
|
reset(); now = UINT32_MAX - 49; send(&hcan1); now = 49;
|
||||||
|
ServiceCanHealth(&hcan1); assert(abort_calls == 0);
|
||||||
|
now = 50; ServiceCanHealth(&hcan1); assert(abort_calls == 1); /* tick wrap */
|
||||||
|
reset(); send(&hcan1); regs[0].ESR = CAN_ESR_BOFF | CAN_ESR_EPVF;
|
||||||
|
send(&hcan1); ServiceCanHealth(&hcan1);
|
||||||
|
assert(can_diagnostics[0].bus_off_events == 1 && can_diagnostics[0].passive_events == 1);
|
||||||
|
assert(can_diagnostics[0].tx_dropped == 1 && abort_calls == 1);
|
||||||
|
regs[0].pending = 0; regs[0].ESR = 0; send(&hcan1);
|
||||||
|
assert(can_diagnostics[0].recovery_events == 1 && can_diagnostics[0].tx_queued == 2);
|
||||||
|
reset(); send(&hcan1); send(&hcan1); send(&hcan1); send(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_queued == 3 && can_diagnostics[0].tx_dropped == 1);
|
||||||
|
reset(); add_result = HAL_ERROR; send(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_dropped == 1 && !can_tx_tracked[0][0]);
|
||||||
|
reset(); send(&hcan1); now = 100; abort_result = HAL_ERROR; ServiceCanHealth(&hcan1);
|
||||||
|
assert(can_diagnostics[0].abort_failures == 1 && !can_tx_abort_requested[0][0]);
|
||||||
|
abort_result = HAL_OK; ServiceCanHealth(&hcan1); assert(can_diagnostics[0].abort_requests == 1);
|
||||||
|
reset(); send(&hcan1); race_completion = true; send(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_unobserved == 1 && can_diagnostics[0].tx_queued == 2);
|
||||||
|
/* Validate success bit selection for mailboxes 1 and 2. */
|
||||||
|
reset(); send(&hcan1); send(&hcan1); send(&hcan1);
|
||||||
|
regs[0].pending = 0; regs[0].TSR = (CAN_TSR_TXOK0 << 8) | (CAN_TSR_TXOK0 << 16);
|
||||||
|
ServiceCanHealth(&hcan1);
|
||||||
|
assert(can_diagnostics[0].tx_completed == 2 && can_diagnostics[0].tx_failed == 1);
|
||||||
|
puts("CAN health tests passed");
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
with tempfile.TemporaryDirectory() as temp:
|
||||||
|
root = Path(temp)
|
||||||
|
(root / 'test.c').write_text(harness)
|
||||||
|
subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror',
|
||||||
|
'-fsanitize=undefined', str(root / 'test.c'), '-o', str(root / 'test')], check=True)
|
||||||
|
subprocess.run([str(root / 'test')], check=True)
|
||||||
@@ -74,6 +74,12 @@ ADC=12300mV WheelSpeed=LF:0.289m/s RF:0.577m/s
|
|||||||
|
|
||||||
CAN1 和 CAN2 使用相同协议,所有主动上报均分别发送到两条总线。使用 11 位标准数据帧、低字节在前(little-endian)。CAN 正常发帧必须存在至少一个其他节点 ACK;单独接 CAN 分析仪且未开启 ACK 时,发送邮箱可能不会完成。
|
CAN1 和 CAN2 使用相同协议,所有主动上报均分别发送到两条总线。使用 11 位标准数据帧、低字节在前(little-endian)。CAN 正常发帧必须存在至少一个其他节点 ACK;单独接 CAN 分析仪且未开启 ACK 时,发送邮箱可能不会完成。
|
||||||
|
|
||||||
|
两路 CAN 均开启硬件自动 Bus-Off 恢复(ABOM)。总线重新满足恢复条件后,无需 MCU RESET 或整车断电即可重新通信;恢复功能不能消除线束、终端、节点或位时序造成的错误。
|
||||||
|
|
||||||
|
主循环分别维护两路发送邮箱:Bus-Off 时停止入队并请求取消未完成帧;正常状态下超过 100 ms 仍未完成的帧也会被异步取消,避免未连接的 CAN2 或缺少 ACK 时积压旧上报和 DAC 应答。邮箱满或入队失败的帧直接丢弃并统计,不阻塞另一条 CAN,也不等待取消完成。周期上报在后续周期重新生成;DAC 应答不保证重发。
|
||||||
|
|
||||||
|
可通过 STLINK/GDB 读取 `can_diagnostics[0]`(CAN1)和 `[1]`(CAN2):`last_esr` 为最近的硬件错误状态,`bus_off_events` / `recovery_events` / `passive_events` 为主循环观察到的状态转换次数,`tx_queued` 为入队数,`tx_completed` 为硬件 TXOK 完成数,`tx_failed` 为未成功完成数(含取消),`tx_unobserved` 为在邮箱重新分配前未能采到完成结果的帧数,`tx_dropped` 为未入队数,`abort_requests` / `abort_failures` 为取消请求成功/失败次数。计数上电清零,按 uint32 回绕;轮询可能漏掉短暂状态转换。ESR 的 TEC 字段只有发送错误计数的低 8 位,Bus-Off 时不能将它当作完整计数。TXOK 仍需外部抓包验证报文内容和持续通信。
|
||||||
|
|
||||||
| ID | 名称 | 周期/触发 | 数据 |
|
| ID | 名称 | 周期/触发 | 数据 |
|
||||||
|---:|---|---|---|
|
|---:|---|---|---|
|
||||||
| `0x6A0` | 心跳 | 每 100 ms | `A1 01 seq_L seq_H uptime_0..3` |
|
| `0x6A0` | 心跳 | 每 100 ms | `A1 01 seq_L seq_H uptime_0..3` |
|
||||||
|
|||||||
Reference in New Issue
Block a user