fix: recover CAN bus-off and expire stalled transmissions

This commit is contained in:
2026-10-11 16:01:34 +08:00
parent 7bc2d9f1ea
commit 080129b0d5
3 changed files with 248 additions and 3 deletions
+119 -3
View File
@@ -65,6 +65,7 @@ typedef struct
#define ADC_REPORT_PERIOD_MS 500U
#define PWM_REPORT_PERIOD_MS 20U
#define GPIO_REPORT_PERIOD_MS 1000U
#define CAN_TX_TIMEOUT_MS 100U
#define CAN_HEARTBEAT_PERIOD_MS 100U
#define DAC_CAN_TIMEOUT_MS 100U
#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 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 */
/* Private function prototypes -----------------------------------------------*/
@@ -167,6 +188,7 @@ static uint32_t ReadAdcInputMillivolts(void);
static uint32_t WheelSpeedMmPerSecond(uint32_t frequency_hz);
static uint32_t GetTimerClockHz(const TIM_HandleTypeDef *htim);
static void InitCaptureChannels(void);
static void ServiceCanHealth(CAN_HandleTypeDef *hcan);
static void ConfigureCanRuntime(CAN_HandleTypeDef *hcan);
static void StartCanBus(CAN_HandleTypeDef *hcan, uint32_t filter_bank);
static void SendCanFrame(CAN_HandleTypeDef *hcan,
@@ -237,6 +259,8 @@ int main(void)
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ServiceCanHealth(&hcan1);
ServiceCanHealth(&hcan2);
PollUart();
PollCanBus(&hcan1, "CAN1");
PollCanBus(&hcan2, "CAN2");
@@ -366,7 +390,7 @@ static void MX_CAN1_Init(void)
hcan1.Init.TimeSeg1 = CAN_BS1_1TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoBusOff = ENABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
@@ -403,7 +427,7 @@ static void MX_CAN2_Init(void)
hcan2.Init.TimeSeg1 = CAN_BS1_1TQ;
hcan2.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan2.Init.TimeTriggeredMode = DISABLE;
hcan2.Init.AutoBusOff = DISABLE;
hcan2.Init.AutoBusOff = ENABLE;
hcan2.Init.AutoWakeUp = DISABLE;
hcan2.Init.AutoRetransmission = 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)
{
hcan->Init.Prescaler = 4U;
hcan->Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan->Init.TimeSeg1 = CAN_BS1_5TQ;
hcan->Init.TimeSeg2 = CAN_BS2_2TQ;
hcan->Init.AutoBusOff = ENABLE;
hcan->Init.AutoRetransmission = ENABLE;
if (HAL_CAN_Init(hcan) != HAL_OK)
@@ -1340,6 +1426,16 @@ static void SendCanFrame(CAN_HandleTypeDef *hcan,
{
CAN_TxHeaderTypeDef tx_header = {0};
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.ExtId = 0U;
@@ -1348,7 +1444,27 @@ static void SendCanFrame(CAN_HandleTypeDef *hcan,
tx_header.DLC = (length <= 8U) ? length : 8U;
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)