Описание: Библиотека STM32 HAL функционирует HAL_CAN_Transmit_IT
и HAL_CAN_Receive_IT
не может эффективно использоваться одновременно без риска потери данных.
Подробности:
Когда вы строите цикл приема/передачи, как показано ниже (упрощенно)
main() {
HAL_CAN_Receive_IT();
HAL_CAN_Transmit_IT();
}
HAL_CAN_RxCpltCallback() {
HAL_CAN_Receive_IT(); // Rearm receive
}
HAL_CAN_TxCpltCallback() {
HAL_CAN_Transmit_IT(); // Rearm transmit
}
В некоторых ситуациях HAL_CAN_Receive_IT
/ HAL_CAN_Transmit_IT
падает с состоянием занятости. Это происходит из-за того, что и для передачи, и для приема используется блокировка через __HAL_LOCK(hcan)
.
Когда вы звоните HAL_CAN_Transmit_IT
и HAL_CAN_RxCpltCallback
происходит прерывание, состояние блокируется HAL_CAN_Transmit_IT
, и перезапуск rx завершается неудачно.
Каково решение, чтобы решить эту проблему?
Я не могу найти легкий путь сейчас. На мой взгляд, общей ошибкой является унифицированное HAL_CAN_StateTypeDef State
использование трех независимых флагов — общего состояния CAN, состояния rx и состояния tx.
Я думаю, что решение состоит в том, чтобы разделить состояние для {State, rxState и txState} и никогда не блокировать одно и то же в обоих приемах/передачах.
Например, существующая структура,
typedef enum
{
HAL_CAN_STATE_RESET = 0x00, /*!< CAN not yet initialized or disabled */
HAL_CAN_STATE_READY = 0x01, /*!< CAN initialized and ready for use */
HAL_CAN_STATE_BUSY = 0x02, /*!< CAN process is ongoing */
HAL_CAN_STATE_BUSY_TX = 0x12, /*!< CAN process is ongoing */
HAL_CAN_STATE_BUSY_RX = 0x22, /*!< CAN process is ongoing */
HAL_CAN_STATE_BUSY_TX_RX = 0x32, /*!< CAN process is ongoing */
HAL_CAN_STATE_TIMEOUT = 0x03, /*!< CAN in Timeout state */
HAL_CAN_STATE_ERROR = 0x04 /*!< CAN error state */
}HAL_CAN_StateTypeDef;
typedef struct
{
...
__IO HAL_CAN_StateTypeDef State; /*!< CAN communication state */
...
}CAN_HandleTypeDef;
разделить на
typedef enum
{
HAL_CAN_STATE_RESET = 0x00, /*!< CAN not yet initialized or disabled */
HAL_CAN_STATE_READY = 0x01, /*!< CAN initialized and ready for use */
HAL_CAN_STATE_BUSY = 0x02, /*!< CAN process is ongoing */
}HAL_CAN_StateTypeDef;
typedef enum
{
HAL_CAN_TXRX_STATE_READY = 0x01,
HAL_CAN_TXRX_STATE_BUSY = 0x02,
HAL_CAN_TXRX_STATE_TIMEOUT = 0x03,
HAL_CAN_TXRX_STATE_ERROR = 0x04
}HAL_CAN_TxRxStateTypeDef;
typedef struct
{
...
__IO HAL_CAN_StateTypeDef State; /*!< CAN communication state */
__IO HAL_CAN_TxRxStateTypeDef RxState; /*!< CAN RX communication state */
__IO HAL_CAN_TxRxStateTypeDef TxState; /*!< CAN TX communication state */
...
}CAN_HandleTypeDef;
Но это потрясающая модификация библиотеки. Может быть, существует лучшее решение? Та же проблема затрагивает и библиотеку USART, я думаю.
Для повторного приема RX вы можете использовать __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_FMP0); // установить флаг прерывания для RX FIFO0.
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *CanHandle)
{
__HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_FMP0);
}
или
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *CanHandle)
{
if (HAL_CAN_Receive_IT(CanHandle, CAN_FIFO0) != HAL_OK)
{
__HAL_CAN_ENABLE_IT(CanHandle, CAN_IT_FMP0); // set interrupt flag for RX FIFO0 if CAN locked
}
}
вызвать, например, stm32f1xx_hal_can.c:
HAL_StatusTypeDef HAL_CAN_Receive_IT(CAN_HandleTypeDef* hcan, uint8_t FIFONumber)
{
/* Check the parameters */
assert_param(IS_CAN_FIFO(FIFONumber));
if((hcan->State == HAL_CAN_STATE_READY) || (hcan->State == HAL_CAN_STATE_BUSY_TX))
{
/* Process locked */
__HAL_LOCK(hcan); // <<----see define of __HAL_LOCK, this contains return command, wtf????????
if(hcan->State == HAL_CAN_STATE_BUSY_TX)
{
/* Change CAN state */
hcan->State = HAL_CAN_STATE_BUSY_TX_RX;
}
else
{
/* Change CAN state */
hcan->State = HAL_CAN_STATE_BUSY_RX;
}
/* Set CAN error code to none */
hcan->ErrorCode = HAL_CAN_ERROR_NONE;
/* Enable interrupts: */
/* - Enable Error warning Interrupt */
/* - Enable Error passive Interrupt */
/* - Enable Bus-off Interrupt */
/* - Enable Last error code Interrupt */
/* - Enable Error Interrupt */
/* - Enable Transmit mailbox empty Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_EWG |
CAN_IT_EPV |
CAN_IT_BOF |
CAN_IT_LEC |
CAN_IT_ERR |
CAN_IT_TME );
/* Process unlocked */
__HAL_UNLOCK(hcan);
if(FIFONumber == CAN_FIFO0)
{
/* Enable FIFO 0 message pending Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_FMP0); // <<---- here the rearm interrupt flag for FIFO0, if can is locked, function exits on __HAL_LOCK and newer comes here!
}
else
{
/* Enable FIFO 1 message pending Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_FMP1);
}
}
else
{
return HAL_BUSY;
}
/* Return function status */
return HAL_OK;
}
Как раб, вы бы использовали это как слушатель-ответчик. Как мастер, вы бы использовали его в качестве передатчика-слушателя.
Суть в том, что в любой момент вы знаете, что вам следует делать, либо слушать, либо передавать.
Я обнаружил, что ST HAL отлично подходит для быстрого старта, но если вы отклоняетесь от нескольких конкретных вариантов использования, он становится очень сложным. Я не уверен, какое это имеет значение в данном случае, потому что CAN является полудуплексным.
HAL_CAN_Transmit_IT
и HAL_CAN_Receive_IT
работать с прерываниями. Я бы посоветовал вам найти тот, который более важен для вас, и использовать прерывание на нем. Например, если вы предпочитаете получать, а не передавать, используйте HAL_CAN_Receive_IT
и HAL_CAN_Transmit
для части передачи.
Благодаря тому, как работает CAN BUS, вы можете получить сообщение в любой момент времени, и поэтому ваше прерывание приема будет очень занятым.
В моей собственной реализации мне нравится использовать функции HAL_CAN_Transmit
и HAL_CAN_Receive
без прерываний, потому что мои реализации не используют кадры удаленных запросов, и до сих пор у меня не было необходимости фактически использовать прерывание для приема или передачи сообщений CAN.
Дмитрий
Даниэль