【双 MCU 架构车载仪表终端(CAN 互联)】STM32H7 <-> STM32F1 双板 CAN 总线收发

1. 引言

进行完两个板子各自的回环收发测试的时候,证明两个板子的 CAN 收发大体没有什么问题,这时就可以进行双板的收发了。不过有些事项还是需要小小注意一下的,下文会提到。

2. 硬件连接

 ┌───────────────────────────┐                ┌───────────────────────────┐
 │   STM32F103C8T6 (节点1)    │                │  STM32H757XIH6 (节点2)     │
 │   CAN1 / bxCAN, 1Mbit/s   │                │   FDCAN1, Classic, 1Mbit/s│
 │                           │                │                           │
 │   PA12 ───────── CAN_TX   │                │   CAN_TX ───────── PB9    │
 │   PA11 ───────── CAN_RX   │                │   CAN_RX ───────── PB8    │
 └───────┬───────────────┬───┘                └───┬───────────────┬───────┘
         │ TXD           │ RXD                    │ RXD           │ TXD
         ▼               ▲                        ▲               ▼
 ┌───────┴───────────────┴───┐                ┌───┴───────────────┴───────┐
 │  CAN 收发器                │                │  CAN 收发器 #2             │
 └───────┬───────────────┬───┘                └───┬───────────────┬───────┘
         │ CAN_H         │ CAN_L                  │ CAN_L         │ CAN_H
 ────────┴───────────────┴────────┬───────────────┴───────────────┴─────────
                                  │
                        (双绞线 CAN_H / CAN_L)

3. STM32H7 侧

3.1 CubeMX 配置

FDCAN 配置:
image

中断配置:
image

3.2 代码部分

3.2.1 过滤、中断、FDCAN手动使能

/* USER CODE BEGIN FDCAN1_Init 2 */
/* 全局过滤器:非匹配标准/扩展帧收进 RX FIFO0,远程帧拒收 */
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,
                                 FDCAN_ACCEPT_IN_RX_FIFO0,
                                 FDCAN_ACCEPT_IN_RX_FIFO0,
                                 FDCAN_REJECT,
                                 FDCAN_REJECT) != HAL_OK)
{
  Error_Handler();
}
/* 使能中断通知 */
if (HAL_FDCAN_ActivateNotification(&hfdcan1,
                                   FDCAN_IT_RX_FIFO0_NEW_MESSAGE | // 接收新报文事件
                                       FDCAN_IT_TX_COMPLETE |      // 发送完成事件
                                       FDCAN_IT_TX_ABORT_COMPLETE, // 发送取消完成事件
                                   FDCAN_TX_BUFFER0) != HAL_OK)
{
  Error_Handler();
}
/* 启动 CAN */
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
{
  Error_Handler();
}
/* USER CODE END FDCAN1_Init 2 */

3.2.2 app_fdcan.c

#include "app_fdcan.h"
#include "mylog.h"
#include "fdcan.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os2.h"
#include <string.h>

#define FDCAN_RX_BUF_SIZE 16 // 软件接收缓冲区深度(帧),16 对单帧间隔收发足够

typedef struct
{
    uint32_t ID;     // 报文 ID(标准或扩展)
    uint8_t Length;  // 数据长度
    uint8_t Data[8]; // 数据域
} CAN_FrameTypeDef;

static CAN_FrameTypeDef FDCAN_RxBuf[FDCAN_RX_BUF_SIZE];    // 环形缓冲区
static volatile uint8_t FDCAN_RxHead = 0;                  // 写指针,仅中断回调修改
static volatile uint8_t FDCAN_RxTail = 0;                  // 读指针,仅主循环修改
static volatile uint32_t FDCAN_TxCompleteBuffers = 0;      // 传输缓冲区完成标志位
static volatile uint32_t FDCAN_TxAbortCompleteBuffers = 0; // 传输缓冲区中止完成标志位
static volatile uint32_t FDCAN_RxFifoFull = 0;             // FIFO 满标志位
static volatile uint32_t FDCAN_ErrorCode = 0;              // 错误码
static volatile uint32_t FDCAN_ErrorStatus = 0;            // 错误状态改变标志

static void FDCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *TxData)
{
    FDCAN_TxHeaderTypeDef TxMassage;

    TxMassage.BitRateSwitch = FDCAN_BRS_OFF;
    TxMassage.DataLength = Length;
    TxMassage.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
    TxMassage.FDFormat = FDCAN_CLASSIC_CAN;
    TxMassage.Identifier = ID;
    TxMassage.IdType = FDCAN_STANDARD_ID;
    TxMassage.MessageMarker = 0xAA;
    TxMassage.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
    TxMassage.TxFrameType = FDCAN_DATA_FRAME;

    /* 将消息添加到专用Tx缓冲区 */
    if (HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &TxMassage, TxData, FDCAN_TX_BUFFER0) != HAL_OK)
    {
        /* 发送失败:多半是 Tx FIFO 满(8 元素),直接返回 */
        LOG_ERROR("FDCAN", "AddMessageToTxBuffer failed");
        return;
    }
    LOG_INFO("FDCAN", "AddMessageToTxBuffer success");

    /* 启用传输请求 */
    if (HAL_FDCAN_EnableTxBufferRequest(&hfdcan1, FDCAN_TX_BUFFER0) != HAL_OK)
    {
        LOG_ERROR("FDCAN", "EnableTxBufferRequest failed");
        return;
    }
    LOG_INFO("FDCAN", "EnableTxBufferRequest success");
}

/**
 * @brief 检查软件接收缓冲区是否为空
 * @return uint8_t 1: 为空, 0: 不为空
 */
static uint8_t FDCAN_FIFO_isEmpty(void)
{
    if (FDCAN_RxHead == FDCAN_RxTail) // 软件缓冲区为空
    {
        LOG_WARN("FDCAN", "FIFO is empty");
        return 1;
    }
    LOG_INFO("FDCAN", "FIFO is not empty");
    return 0;
}

/**
 * @brief 接收 CAN 消息
 * @param ID 标准 ID 号指针
 * @param Length 数据长度指针
 * @param RxData 数据指针
 */
static void FDCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *RxData)
{
    CAN_FrameTypeDef *frame = &FDCAN_RxBuf[FDCAN_RxTail];

    *ID = frame->ID;
    *Length = frame->Length;
    memcpy(RxData, frame->Data, frame->Length);

    FDCAN_RxTail = (FDCAN_RxTail + 1) % FDCAN_RX_BUF_SIZE;
}

static void FDCAN_ProcessEvents(void)
{
    uint32_t completeBuffers = FDCAN_TxCompleteBuffers;           // 复制传输缓冲区完成标志位
    uint32_t abortCompleteBuffers = FDCAN_TxAbortCompleteBuffers; // 复制传输缓冲区中止完成标志位
    uint32_t rxFifoFull = FDCAN_RxFifoFull;                       // 复制接收 FIFO 满标志位
    uint32_t errorCode = FDCAN_ErrorCode;                         // 复制错误码
    uint32_t errorStatus = FDCAN_ErrorStatus;                     // 复制错误状态改变标志位

    FDCAN_TxCompleteBuffers = 0;      // 清空传输缓冲区完成标志位
    FDCAN_TxAbortCompleteBuffers = 0; // 清空传输缓冲区中止完成标志位
    FDCAN_RxFifoFull = 0;             // 清空接收 FIFO 满标志位
    FDCAN_ErrorCode = 0;              // 清空错误码
    FDCAN_ErrorStatus = 0;            // 清空错误状态改变标志位

    /* 接收 FIFO 满标志位 */
    if ((rxFifoFull & FDCAN_IT_RX_FIFO0_FULL) != 0U) // FIFO0 满
    {
        LOG_WARN("FDCAN", "CallBack: CAN RxFIFO0 Full");
    }

    /* 处理传输缓冲区完成标志位 */
    if ((completeBuffers & FDCAN_TX_BUFFER0) != 0U) // 传输缓冲区 0 完成
    {
        LOG_INFO("FDCAN", "CallBack: Tx Buffer 0 Complete");
    }
    if ((completeBuffers & FDCAN_TX_BUFFER1) != 0U) // 传输缓冲区 1 完成
    {
        LOG_INFO("FDCAN", "CallBack: Tx Buffer 1 Complete");
    }
    if ((completeBuffers & FDCAN_TX_BUFFER2) != 0U) // 传输缓冲区 2 完成
    {
        LOG_INFO("FDCAN", "CallBack: Tx Buffer 2 Complete");
    }

    /* 处理传输缓冲区中止完成标志位 */
    if ((abortCompleteBuffers & FDCAN_TX_BUFFER0) != 0U) // 传输缓冲区 0 中止完成
    {
        LOG_WARN("FDCAN", "CallBack: Tx Buffer 0 Abort");
    }
    if ((abortCompleteBuffers & FDCAN_TX_BUFFER1) != 0U) // 传输缓冲区 1 中止完成
    {
        LOG_WARN("FDCAN", "CallBack: Tx Buffer 1 Abort");
    }
    if ((abortCompleteBuffers & FDCAN_TX_BUFFER2) != 0U) // 传输缓冲区 2 中止完成
    {
        LOG_WARN("FDCAN", "CallBack: Tx Buffer 2 Abort");
    }

    /* 处理错误码 */
    if (errorCode != FDCAN_PROTOCOL_ERROR_NONE) // 错误码不为无错误
    {
        LOG_ERROR("FDCAN", "CallBack: Error: 0x%08lX", errorCode);
    }
    /* 处理错误状态改变标志位 */
    if (errorStatus != FDCAN_PROTOCOL_ERROR_NONE) // 错误状态改变
    {
        LOG_ERROR("FDCAN", "CallBack: Error Status: 0x%08lX", errorStatus);
    }
}

/**
 * @brief CAN 任务线程
 * @param pvParameters 任务参数指针,本例中未使用
 */
static void FDCAN_Thread(void *pvParameters)
{
    for (;;)
    {
        osDelay(1000);
        FDCAN_Transmit(0x123, 8, (uint8_t *)"Hello!");
        if (!FDCAN_FIFO_isEmpty())
        {
            uint32_t ID = 0;
            uint8_t Length = 0;
            uint8_t RxData[8] = {0};
            FDCAN_Receive(&ID, &Length, RxData);
            LOG_INFO("FDCAN", "ID: %d, Length: %d, Data: %02X %02X %02X %02X %02X %02X %02X %02X",
                     ID, Length,
                     RxData[0],
                     RxData[1],
                     RxData[2],
                     RxData[3],
                     RxData[4],
                     RxData[5],
                     RxData[6],
                     RxData[7]);
        }
        FDCAN_ProcessEvents();
    }
}

/**
 * @brief 启动 CAN 任务线程
 */
void FDCAN_Thread_Start(void)
{
    BaseType_t xReturn = pdPASS;

    xReturn = xTaskCreate((TaskFunction_t)FDCAN_Thread,
                          (char *)"FDCAN_Thread",
                          (uint16_t)1024,
                          (void *)NULL,
                          (uint8_t)5,
                          (TaskHandle_t *)NULL);
    if (xReturn != pdPASS)
    {
        LOG_ERROR("FDCAN", "task create failed");
    }
}

/**
 * @brief FIFO0 收到报文的中断回调
 */
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
    FDCAN_RxHeaderTypeDef RxMessage;
    CAN_FrameTypeDef frame;

    if (hfdcan->Instance == FDCAN1)
    {
        switch (RxFifo0ITs)
        {
        case FDCAN_IT_RX_FIFO0_MESSAGE_LOST: // 报文丢失事件
            break;
        case FDCAN_IT_RX_FIFO0_FULL: // FIFO 满事件
            break;
        case FDCAN_IT_RX_FIFO0_WATERMARK: // 水位事件
            break;
        case FDCAN_IT_RX_FIFO0_NEW_MESSAGE: // 新报文事件
            /* 从 Rx FIFO 0 读取消息 */
            if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxMessage, frame.Data) == HAL_OK)
            {
                frame.ID = RxMessage.Identifier;     // ID 已是纯数值,不用再按 IDE 挑 StdId/ExtId
                frame.Length = RxMessage.DataLength; // 经典帧 0~8 字节时 DLC 码即字节数

                uint8_t next = (FDCAN_RxHead + 1) % FDCAN_RX_BUF_SIZE;
                if (next != FDCAN_RxTail) // 缓冲区未满才入队,满了丢弃 newest
                {
                    FDCAN_RxBuf[FDCAN_RxHead] = frame;
                    FDCAN_RxHead = next;
                }
            }
            break;
        default:
            break;
        }
    }
}

/**
 * @brief 传输缓冲区完成中断回调
 * @param hfdcan CAN 处理结构体指针
 * @param BufferIndexes 传输缓冲区索引
 */
void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
{
    if (hfdcan->Instance == FDCAN1)
    {
        switch (BufferIndexes)
        {
        case FDCAN_TX_BUFFER0:
            FDCAN_TxCompleteBuffers = FDCAN_TX_BUFFER0;
            break;
        default:
            break;
        }
    }
}

/**
 * @brief 传输缓冲区中止中断回调
 * @param hfdcan CAN 处理结构体指针
 * @param BufferIndexes 传输缓冲区索引
 */
void HAL_FDCAN_TxBufferAbortCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
{
    if (hfdcan->Instance == FDCAN1)
    {
        switch (BufferIndexes)
        {
        case FDCAN_TX_BUFFER0:
            FDCAN_TxAbortCompleteBuffers = FDCAN_TX_BUFFER0;
            break;
        default:
            break;
        }
    }
}

/**
 * @brief 错误中断回调
 * @param hfdcan CAN 处理结构体指针
 */
void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan->Instance == FDCAN1)
    {
        FDCAN_ErrorCode = HAL_FDCAN_GetError(hfdcan);
        hfdcan->ErrorCode = HAL_FDCAN_ERROR_NONE; // 读后立即清除,同一错误只经中断上报一次
    }
}

/**
 * @brief 错误状态中断回调
 * @param hfdcan CAN 处理结构体指针
 * @param ErrorStatusITs 错误状态中断标志位
 */
void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs)
{
    if (hfdcan->Instance == FDCAN1)
    {
        switch (ErrorStatusITs)
        {
        case FDCAN_IT_ERROR_PASSIVE:
            FDCAN_ErrorStatus = FDCAN_IT_ERROR_PASSIVE;
            break;
        case FDCAN_IT_ERROR_WARNING:
            FDCAN_ErrorStatus = FDCAN_IT_ERROR_WARNING;
            break;
        case FDCAN_IT_BUS_OFF:
            FDCAN_ErrorStatus = FDCAN_IT_BUS_OFF;
            break;
        default:
            break;
        }
    }
}

3.2.4 app_fdcan.h

#ifndef __APP_FDCAN_H__
#define __APP_FDCAN_H__

#include "main.h"

void FDCAN_Thread_Start(void);

#endif

3.3 关键点注意

FDCAN 与 bxCAN 相反:无过滤器命中时,全局过滤器(RXGFC 复位默认)会拒收所有非匹配帧,报文无法进入 RX FIFO0,相当于“全阻”。因此,这里应直接配置全局过滤器,不要只单独失能过滤器;即使失能了,也要用全局过滤器配置兜底。

禁用过滤器:

FDCAN_FilterTypeDef FDCAN_FilterInitStruct;
FDCAN_FilterInitStruct.FilterConfig = FDCAN_FILTER_DISABLE; // 禁用过滤器

配置全局过滤器:

if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,
                                 FDCAN_ACCEPT_IN_RX_FIFO0,
                                 FDCAN_ACCEPT_IN_RX_FIFO0,
                                 FDCAN_REJECT,
                                 FDCAN_REJECT) != HAL_OK)
{
  Error_Handler();
}

3.4 串口打印日志

[I][APP] APP Boot ============================================================================
[I][SDRAM] SDRAM init OK
[I][LCD] LCD ID:4384
[I][LCD] LCD init OK
[I][SYSTEM] main() user init OK
[I][RTOS] FreeRTOS init OK
[I][SYSTEM] running
[I][FDCAN] AddMessageToTxBuffer success
[I][FDCAN] EnableTxBufferRequest success
[I][FDCAN] FIFO is not empty
[I][FDCAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][FDCAN] CallBack: Tx Buffer 0 Complete
[I][SYSTEM] running
[I][FDCAN] AddMessageToTxBuffer success
[I][FDCAN] EnableTxBufferRequest success
[I][FDCAN] FIFO is not empty
[I][FDCAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][FDCAN] CallBack: Tx Buffer 0 Complete
[I][SYSTEM] running
[I][FDCAN] AddMessageToTxBuffer success
[I][FDCAN] EnableTxBufferRequest success
[I][FDCAN] FIFO is not empty
[I][FDCAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][FDCAN] CallBack: Tx Buffer 0 Complete
[I][SYSTEM] running
[I][FDCAN] AddMessageToTxBuffer success
[I][FDCAN] EnableTxBufferRequest success
[I][FDCAN] FIFO is not empty
[I][FDCAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][FDCAN] CallBack: Tx Buffer 0 Complete

4. STM32F1 侧

4.1 CubeMX 配置

CAN 配置:
image

中断配置:
image

引脚配置:
image

4.2 代码部分

4.2.1 过滤、中断、CAN 手动使能

/* USER CODE BEGIN CAN_Init 2 */
CAN_FilterTypeDef CAN_FilterInitStruct;
CAN_FilterInitStruct.FilterBank = 0;
/**
 * @brief 四种过滤器
 *        1. 16 位列表模式:四个参数分别存入一组 ID 即可
 *        2. 16 位掩码模式:IdHigh 存入第一组 ID,MaskIdHigh 存入对应掩码;IdLow 存入第二组 ID,MaskIdLow 存入对应掩码
 *        3. 32 位列表模式:IdHigh 和 IdLow 组合到一起,存入第一组 32 位 ID;MaskIdHigh 和 MaskIdLow 组合到一起,存入第二组 32 位 ID
 *        4. 32 位掩码模式:IdHigh 和 IdLow 组合到一起,存入第一组 32 位 ID;MaskIdHigh 和 MaskIdLow 组合到一起,存入 对应掩码位
 *        5. 全通模式:IdHigh 和 IdLow 都存入 0x0000,MaskIdHigh 和 MaskIdLow 都存入 0xFFFF,前提是 32 位掩码模式
 */
CAN_FilterInitStruct.FilterIdHigh = 0x0000;
CAN_FilterInitStruct.FilterIdLow = 0x0000;
CAN_FilterInitStruct.FilterMaskIdHigh = 0x0000;
CAN_FilterInitStruct.FilterMaskIdLow = 0x0000;
CAN_FilterInitStruct.FilterScale = CAN_FILTERSCALE_32BIT;
CAN_FilterInitStruct.FilterMode = CAN_FILTERMODE_IDMASK;
CAN_FilterInitStruct.FilterFIFOAssignment = CAN_FILTER_FIFO0;
CAN_FilterInitStruct.FilterActivation = ENABLE;
if (HAL_CAN_ConfigFilter(&hcan, &CAN_FilterInitStruct) != HAL_OK)
{
  LOG_ERROR("CAN", "ConfigFilter failed");
  Error_Handler();
}

/* 使能发送邮箱、接收 FIFO 0 和 CAN 状态错误中断 */
if (HAL_CAN_ActivateNotification(&hcan,
                                 CAN_IT_TX_MAILBOX_EMPTY |         // 发送邮箱空中断
                                     CAN_IT_RX_FIFO0_MSG_PENDING | // 接收 FIFO 0 消息待处理中断
                                     CAN_IT_RX_FIFO0_FULL |        // 接收 FIFO 0 满中断
                                     CAN_IT_RX_FIFO0_OVERRUN |     // 接收 FIFO 0 溢出中断
                                     CAN_IT_ERROR) != HAL_OK)      // CAN 状态错误中断

{
  LOG_ERROR("CAN", "ActivateNotification failed");
  Error_Handler();
}

/* 启动 CAN 模块 */
if (HAL_CAN_Start(&hcan) != HAL_OK)
{
  LOG_ERROR("CAN", "Start failed");
  Error_Handler();
}
/* USER CODE END CAN_Init 2 */

4.2.2 app_can.c

#include "app_can.h"
#include "mylog.h"
#include "can.h"
#include <string.h>

#define CAN_RX_BUF_SIZE 16 // 软件接收缓冲区深度(帧),16 对单帧间隔收发足够

typedef struct
{
    uint32_t ID;     // 报文 ID(标准或扩展)
    uint8_t Length;  // 数据长度
    uint8_t Data[8]; // 数据域
} CAN_FrameTypeDef;

static CAN_FrameTypeDef CAN_RxBuf[CAN_RX_BUF_SIZE];   // 环形缓冲区
static volatile uint8_t CAN_RxHead = 0;               // 写指针,仅中断回调修改
static volatile uint8_t CAN_RxTail = 0;               // 读指针,仅主循环修改
static volatile uint32_t CAN_TxCompleteMailboxes = 0; // 传输邮箱完成标志位
static volatile uint32_t CAN_TxAbortMailboxes = 0;    // 传输邮箱中止标志位
static volatile uint32_t CAN_RxFifoFull = 0;          // FIFO 满标志位
static volatile uint32_t CAN_ErrorCode = 0;           // 错误码

/**
 * @brief 发送 CAN 消息
 * @param ID 标准 ID 号
 * @param Length 数据长度
 * @param TxData 数据指针
 */
void CAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *TxData)
{
    CAN_TxHeaderTypeDef TxMessage; // 发送消息结构体
    uint32_t TxMailbox;            // 接收返回存储 Tx 消息的邮箱号

    /* 填充帧头信息 */
    TxMessage.StdId = ID;         // 标准 ID 号
    TxMessage.ExtId = ID;         // 扩展 ID 号
    TxMessage.IDE = CAN_ID_STD;   // 扩展 ID 标志位,选择标准 ID 类型
    TxMessage.RTR = CAN_RTR_DATA; // 数据帧 & 遥控帧
    TxMessage.DLC = Length;       // 数据长度

    /* 打印当前空闲邮箱数量 */
    LOG_INFO("CAN", "Free Tx mailbox count: %lu", HAL_CAN_GetTxMailboxesFreeLevel(&hcan));

    /* 将发送消息添加到发送邮箱队列并激活相应的传输请求 */
    uint8_t result = HAL_CAN_AddTxMessage(&hcan, &TxMessage, TxData, &TxMailbox);
    if (result != HAL_OK)
    {
        LOG_ERROR("CAN", "Add TxMessage to Mailbox or Activate transmission request Failed, return");
        return;
    }
    LOG_INFO("CAN", "Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x%02X", TxMailbox);
}

/**
 * @brief 检查软件接收缓冲区是否为空
 * @param None
 * @return uint8_t 1: 为空, 0: 不为空
 */
uint8_t CAN_RxFIFO_isEmpty(void)
{
    if (CAN_RxHead == CAN_RxTail) // 软件缓冲区为空
    {
        LOG_WARN("CAN", "CAN RxFIFO is empty");
        return 1;
    }
    LOG_INFO("CAN", "CAN RxFIFO not empty");
    return 0;
}

/**
 * @brief 接收 CAN 消息
 * @param ID 标准 ID 号指针
 * @param Length 数据长度指针
 * @param RxData 数据指针
 */
void CAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *RxData)
{
    CAN_FrameTypeDef *frame = &CAN_RxBuf[CAN_RxTail];

    *ID = frame->ID;
    *Length = frame->Length;
    memcpy(RxData, frame->Data, frame->Length);

    CAN_RxTail = (CAN_RxTail + 1) % CAN_RX_BUF_SIZE;
}

/**
 * @brief 在主循环中处理 CAN 中断事件
 */
void CAN_ProcessEvents(void)
{
    uint32_t completeMailboxes = CAN_TxCompleteMailboxes; // 复制传输邮箱完成标志位
    uint32_t abortMailboxes = CAN_TxAbortMailboxes;       // 复制传输邮箱中止标志位
    uint32_t rxFifoFull = CAN_RxFifoFull;                 // 复制接收 FIFO 满标志位
    uint32_t errorCode = CAN_ErrorCode;                   // 复制错误码

    CAN_TxCompleteMailboxes = 0; // 清空传输邮箱完成标志位
    CAN_TxAbortMailboxes = 0;    // 清空传输邮箱中止标志位
    CAN_RxFifoFull = 0;          // 清空接收 FIFO 满标志位
    CAN_ErrorCode = 0;           // 清空错误码

    /* 接收 FIFO 满标志位 */
    if ((rxFifoFull & CAN_IT_RX_FIFO0_FULL) != 0U) // FIFO0 满
    {
        LOG_WARN("CAN", "Callback: CAN RxFIFO0 Full");
    }

    /* 处理传输邮箱完成标志位 */
    if ((completeMailboxes & CAN_TX_MAILBOX0) != 0U) // 传输邮箱 0 完成
    {
        LOG_INFO("CAN", "Callback: Tx Mailbox 0 Complete");
    }
    if ((completeMailboxes & CAN_TX_MAILBOX1) != 0U) // 传输邮箱 1 完成
    {
        LOG_INFO("CAN", "Callback: Tx Mailbox 1 Complete");
    }
    if ((completeMailboxes & CAN_TX_MAILBOX2) != 0U) // 传输邮箱 2 完成
    {
        LOG_INFO("CAN", "Callback: Tx Mailbox 2 Complete");
    }

    /* 处理传输邮箱中止标志位 */
    if ((abortMailboxes & CAN_TX_MAILBOX0) != 0U) // 传输邮箱 0 中止
    {
        LOG_WARN("CAN", "Callback: Tx Mailbox 0 Abort");
    }
    if ((abortMailboxes & CAN_TX_MAILBOX1) != 0U) // 传输邮箱 1 中止
    {
        LOG_WARN("CAN", "Callback: Tx Mailbox 1 Abort");
    }
    if ((abortMailboxes & CAN_TX_MAILBOX2) != 0U) // 传输邮箱 2 中止
    {
        LOG_WARN("CAN", "Callback: Tx Mailbox 2 Abort");
    }

    /* 处理错误码 */
    if (errorCode != HAL_CAN_ERROR_NONE) // 错误码不为无错误
    {
        LOG_ERROR("CAN", "Callback: Error: 0x%08lX", errorCode);
    }
}

/**
 * @brief FIFO0 收到报文的中断回调(中断上下文,禁止调用 mylog)
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcanPtr)
{
    CAN_RxHeaderTypeDef RxMessage;
    CAN_FrameTypeDef frame;

    /* 从 FIFO FIFO0 读取消息 */
    if (HAL_CAN_GetRxMessage(hcanPtr, CAN_RX_FIFO0, &RxMessage, frame.Data) == HAL_OK)
    {
        frame.ID = (RxMessage.IDE == CAN_ID_STD) ? RxMessage.StdId : RxMessage.ExtId; // 读出标准 ID 号或扩展 ID 号
        frame.Length = RxMessage.DLC;                                                 // 数据长度

        uint8_t next = (CAN_RxHead + 1) % CAN_RX_BUF_SIZE;
        if (next != CAN_RxTail) // 缓冲区未满才入队,满了丢弃 newest
        {
            CAN_RxBuf[CAN_RxHead] = frame;
            CAN_RxHead = next;
        }
    }
}

/**
 * @brief FIFO0 满回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_RxFifoFull |= CAN_IT_RX_FIFO0_FULL;
}

/**
 * @brief 传输邮箱 0 完成回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxCompleteMailboxes |= CAN_TX_MAILBOX0;
}

/**
 * @brief 传输邮箱 1 完成回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxCompleteMailboxes |= CAN_TX_MAILBOX1;
}

/**
 * @brief 传输邮箱 2 完成回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxCompleteMailboxes |= CAN_TX_MAILBOX2;
}

/**
 * @brief 传输邮箱 0 中止回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxAbortMailboxes |= CAN_TX_MAILBOX0;
}

/**
 * @brief 传输邮箱 1 中止回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxAbortMailboxes |= CAN_TX_MAILBOX1;
}

/**
 * @brief 传输邮箱 2 中止回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan)
{
    (void)hcan;
    CAN_TxAbortMailboxes |= CAN_TX_MAILBOX2;
}

/**
 * @brief 错误回调
 * @param hcan 指向 CAN_HandleTypeDef 结构体的指针
 */
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
    CAN_ErrorCode |= HAL_CAN_GetError(hcan);
}

4.2.3 app_can.h

#ifndef __APP_CAN_H__
#define __APP_CAN_H__

#include "main.h"

void CAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *TxData);
uint8_t CAN_RxFIFO_isEmpty(void);
void CAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *RxData);
void CAN_ProcessEvents(void);

#endif

4.3 关键点注意

STM32F1 CAN 外设的引脚一定要配置成上拉。因为初始化成功是有一个硬性要求,CAN 初始化成功必须要在输入引脚上连续接收11 个隐形电平(逻辑电平 1) 才行,节点在请求 CAN BUS 使用权之前,需要先监听总线的状态,只有总线处于空闲状态的时候,发送节点才能发送起始位(SOF:Start Of Bit),之后,请求发送报文的各个节点进行仲裁,当监听到总线 ≥ 11 Bits 隐性位。也就是说,CAN BUS 由忙碌进入空闲,最快 11 Bits 时间,这里的 11 Bits 对应的 Bit Time = 1/500Kbps = 2us。即:CAN BUS 由忙碌进入空闲的最快 11 * 2us = 22us。如果总线上一直有节点请求发送,则 CAN BUS 会一直处于繁忙的状态。为了区分出每帧 CAN 报文,CAN报文之间需要预留一定空间,即:帧间隔,IFC(Inter Frame Space),由3个隐性位构成。
所以所以,在上电后最好将单片机的 CAN 对应引脚上拉为高电平。

4.4 串口打印日志

[I][APP] APP Boot ============================================================================
[I][SYSTEM] main() user init OK
[I][SYSTEM] running
[I][CAN] Free Tx mailbox count: 3
[I][CAN] Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x01
[I][CAN] CAN RxFIFO not empty
[I][CAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][CAN] Callback: Tx Mailbox 0 Complete
[I][SYSTEM] running
[I][CAN] Free Tx mailbox count: 3
[I][CAN] Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x01
[I][CAN] CAN RxFIFO not empty
[I][CAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][CAN] Callback: Tx Mailbox 0 Complete
[I][SYSTEM] running
[I][CAN] Free Tx mailbox count: 3
[I][CAN] Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x01
[I][CAN] CAN RxFIFO not empty
[I][CAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][CAN] Callback: Tx Mailbox 0 Complete
[I][SYSTEM] running
[I][CAN] Free Tx mailbox count: 3
[I][CAN] Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x01
[I][CAN] CAN RxFIFO not empty
[I][CAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][CAN] Callback: Tx Mailbox 0 Complete
[I][SYSTEM] running
[I][CAN] Free Tx mailbox count: 3
[I][CAN] Add TxMessage to Mailbox and Activate transmission request Succeed, Mailbox: 0x01
[I][CAN] CAN RxFIFO not empty
[I][CAN] ID: 291, Length: 8, Data: 48 65 6C 6C 6F 21 00 00
[I][CAN] Callback: Tx Mailbox 0 Complete

博客导航

博客导航

posted @ 2026-11-03 19:54  膝盖中箭卫兵  阅读(2)  评论(0)    收藏  举报
ORCID iD icon https://orcid.org/0000-0001-5102-772X