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 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)
|
||||
|
||||
Reference in New Issue
Block a user