【双 MCU 架构车载仪表终端(CAN 互联)】STM32H7 FDCAN classic 模式回环收发测试

1. 引言

在进行双机互发之前,必须单机本地进行回环测试,以确保发送和接收数据没有问题。

2. FDCAN CubeMX 配置

基础参数配置:
image

参数解释:

  • Basic Parameters【基础参数】

    • Frame Format:帧格式。选 Classic=传统 CAN 2.0,数据场最长 8 字节;选 FD mode=数据场最长 64 字节,全帧速率一致;选 FD with BRS=数据段可切换到更高速率(通常所说的 CAN FD)。
    • Mode:工作模式。选 Internal LoopBack=发送帧在芯片内部直接回送接收通路,不驱动 PB8/PB9 引脚,无需外部收发器,适合自测;选 Normal=正常收发;选 Restricted=受限模式;选 Monitor=只听模式,仅接收不发送;选 External LoopBack=经引脚和外部收发器回环。
    • Auto Retransmission:自动重传。选 Disable=单次发送(one-shot),仲裁丢失或出错后不再重发,可避免阻塞总线;选 Enable=硬件自动重发直至成功(CAN 默认行为)。
    • Transmit Pause:发送暂停(CCCR.TP 位)。选 Enable=连续发送的两帧之间强制插入 2 个数据位时间的停顿,防止单节点霸占总线;选 Disable=不插入停顿,连续发送。
    • Protocol Exception:协议异常处理(CCCR.PXHD 位)。选 Enable=针对 CAN FD 数据段格式违规按 ISO 规范判为严重错误并进入 Bus-Off;选 Disable=不因此进入 Bus-Off;Classic 模式下影响不大。
    • Message Ram Offset:本实例在 FDCAN 消息 RAM 中的起始偏移(字节,HAL 允许 0~2560)。选 0=使用默认起始偏移;选非 0=指定起始偏移,多个 FDCAN 实例同时激活时用来划分互不重叠的 RAM 区;偏移不当可能与其他实例冲突或越界。
    • Std Filters Nbr:标准 ID 过滤器元素个数(0~128)。选 0=没有标准 ID 匹配入口(M_CAN 架构下报文必须命中过滤器才会进 Rx FIFO/Buffer);选大于 0=分配相应数量的标准 ID 过滤器,数量越多可配置的匹配规则越多,RAM 占用也越大。
    • Ext Filters Nbr:扩展 ID(29 位)过滤器个数(0~64)。选 0=没有扩展 ID 匹配入口;选大于 0=分配相应数量的扩展 ID 过滤器,数量越多可配置的匹配规则越多,RAM 占用也越大。
    • RxFifo0 Elmts Nbr:接收 FIFO0 的元素(槽位)个数(0~64)。选 0=未启用 FIFO0;选大于 0=启用并设置可缓存帧数,数值越大可缓存帧数越多,RAM 占用越大。
    • RxFifo0 Elmt Size:FIFO0 每个元素的数据场长度。选 8 字节=正好容纳 Classic 帧;选更大=可容纳更长的 CAN FD 数据场,RAM 占用增加;长度不足时无法完整容纳长帧数据。
    • RxFifo1 Elmts Nbr:第二个接收 FIFO 的元素个数。选 0=未启用 FIFO1;选大于 0=启用并设置可缓存帧数,数值越大缓存能力越强,RAM 占用越大。
    • RxFifo1 Elmt Size:FIFO1 每个元素的数据场长度。选 8 字节=适合 Classic 帧;选更大=适合 CAN FD 长帧,RAM 占用增加。
    • Rx Buffers Nbr:专用接收缓冲区个数(0~64)。选 0=未启用专用接收缓冲区;选大于 0=启用固定位置的专用接收缓冲,由过滤器指定投递目标,便于覆盖写,数量越多可分配的专用目标越多。
    • Rx Buffer Size:专用接收缓冲区元素长度。选 8 字节=适合 Classic 帧;选更大=适合 CAN FD 长帧,RAM 占用增加。
    • Tx Events Nbr:发送事件 FIFO 元素个数(0~32)。选 0=未启用发送事件回执;选大于 0=记录每帧发送完成的回执(ID、时间戳等),数值越大可保存的完成事件越多。
    • Tx Buffers Nbr:专用发送缓冲区个数。选 0=未启用专用发送缓冲区;选大于 0=启用可单独取消/更新的发送缓冲,数量越多可管理的独立发送缓冲越多。
    • Tx Fifo Queue Elmts Nbr:发送 FIFO/队列的元素个数(0~32)。选 0=未启用发送队列;选大于 0=设置发送队列深度,数值越大最多可排队的待发帧越多。
    • Tx Fifo Queue Mode:发送排队策略。选 FIFO mode=先提交先发送;选 Priority Queue=按报文 ID 优先级高低发送(通常 ID 越小优先级越高)。
    • Tx Elmt Size:发送元素的数据场长度。选 8 字节=足够 Classic 帧使用;选更大=可发送更长的 CAN FD 数据场,RAM 占用增加。
  • Clock Calibration Unit【时钟校准单元】

    • Clock Calibration:时钟校准单元(CCU)。选 Enable=在 CAN FD 高速数据段测量收发位速率偏差并做时钟补偿;选 Disable=不进行补偿;Classic 模式一般用不到,保持 Disable 即可。
  • Bit Timings Parameters【位时序参数】

    • Prescaler (for Time Quantum):预分频器(用于时间份额 tq),对 CAN 内核时钟分频,生成基础时间单位 tq。选较大值=tq 变长,波特率降低;选较小值=tq 变短,波特率提高。
    • Time Quantum:时间份额(tq),CAN 位时序最小时间单位。它由内核时钟和预分频决定:预分频越大,tq 越长;预分频越小,tq 越短。
    • Nominal Time Seg1:位段1的 tq 个数(BS1,包含传播段 + 相位缓冲段1),决定采样点位置。选较大值=采样点后移,单 bit 时间变长;选较小值=采样点前移,单 bit 时间变短。
    • Nominal Time Seg2:位段2的 tq 个数(BS2,相位缓冲段2)。选较大值=单 bit 时间变长、采样点比例相对前移;选较小值=单 bit 时间变短、采样点比例相对后移。
    • Nominal Time for one Bit:单个 bit 总时长(SyncSeg 固定 1 tq + Seg1 + Seg2)。Seg1/Seg2 增大则单 bit 时间变长、波特率降低;减小则单 bit 时间变短、波特率提高。
    • Nominal Baud Rate:标称波特率。由 tq 和单 bit 总 tq 数决定:Prescaler、Seg1、Seg2 增大会降低波特率;减小会提高波特率。
    • Nominal Sync Jump Width:再同步跳转宽度(SJW),总线同步时单次最多调整的 tq 数量。选较大值=允许更大的相位误差补偿;选较小值=同步更严格、补偿范围更小。
    • Data Prescaler:数据段预分频器(CAN FD 数据段/BRS 高速段使用)。选较大值=数据段 tq 变长、数据段速率降低;选较小值=数据段 tq 变短、数据段速率提高;Classic 模式下不生效。
    • Data Time Seg1:数据段位段1的 tq 个数。选较大值=数据段采样点后移、数据段单 bit 时间变长;选较小值=采样点前移、单 bit 时间变短;Classic 模式下不生效。
    • Data Time Seg2:数据段位段2的 tq 个数。选较大值=数据段单 bit 时间变长、采样点比例相对前移;选较小值=单 bit 时间变短、采样点比例相对后移;Classic 模式下不生效。
    • Data Sync Jump Width:数据段再同步跳转宽度。选较大值=数据段允许更大的再同步调整;选较小值=数据段同步更严格、调整范围更小;Classic 模式下不生效。

中断配置:
image

  • FDCAN1 interrupt 0:中断线 0。M_CAN 只有 2 根中断输出线,内部几十个事件源(Rx FIFO0/1 非空、发送完成、错误、总线状态切换等)通过中断线选择寄存器分组路由到这两根线(HAL 中用 HAL_FDCAN_ConfigInterruptLines)
  • FDCAN1 interrupt 1:中断线 1,承接第二组事件,与线 0 配合实现收/发或高/低优先级事件分离
  • FDCAN calibration unit interrupt:时钟校准单元(CCU)的中断,仅在 Clock Calibration 启用时有意义

3. 代码编写

CubeMX 生成代码后,CAN 的过滤器(特别注意,不使能相当于全阻)、中断以及 FDCAN 需要手动使能。

3.1 FDCAN 过滤器配置与使能

在 FDCAN 初始化后的 /* USER CODE BEGIN FDCAN1_Init 2 */ 进行配置和使能 FDCAN 的过滤器。

/* FDCAN1 init function */
void MX_FDCAN1_Init(void)
{

  /* USER CODE BEGIN FDCAN1_Init 0 */

  /* USER CODE END FDCAN1_Init 0 */

  /* USER CODE BEGIN FDCAN1_Init 1 */

  /* USER CODE END FDCAN1_Init 1 */
  hfdcan1.Instance = FDCAN1;
  hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
  hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
  hfdcan1.Init.AutoRetransmission = ENABLE;
  hfdcan1.Init.TransmitPause = DISABLE;
  hfdcan1.Init.ProtocolException = ENABLE;
  hfdcan1.Init.NominalPrescaler = 10;
  hfdcan1.Init.NominalSyncJumpWidth = 2;
  hfdcan1.Init.NominalTimeSeg1 = 8;
  hfdcan1.Init.NominalTimeSeg2 = 3;
  hfdcan1.Init.DataPrescaler = 1;
  hfdcan1.Init.DataSyncJumpWidth = 1;
  hfdcan1.Init.DataTimeSeg1 = 1;
  hfdcan1.Init.DataTimeSeg2 = 1;
  hfdcan1.Init.MessageRAMOffset = 0;
  hfdcan1.Init.StdFiltersNbr = 0;
  hfdcan1.Init.ExtFiltersNbr = 0;
  hfdcan1.Init.RxFifo0ElmtsNbr = 8;
  hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
  hfdcan1.Init.RxFifo1ElmtsNbr = 0;
  hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
  hfdcan1.Init.RxBuffersNbr = 0;
  hfdcan1.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
  hfdcan1.Init.TxEventsNbr = 0;
  hfdcan1.Init.TxBuffersNbr = 1;
  hfdcan1.Init.TxFifoQueueElmtsNbr = 0;
  hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_QUEUE_OPERATION;
  hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_8;
  if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
  {
    Error_Handler();
  }
  /* 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();
  }
  /* USER CODE END FDCAN1_Init 2 */

}

3.2 使能中断

使能过滤器后加上 FDCAN FIFO0 接收新消息中断的使能:

if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
  Error_Handler();
}

3.3 使能 FDCAN 外设

使能中断后使能 FDCAN 外设,启动 FDCAN:

if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
{
  Error_Handler();
}

3.4 发送与接收代码编写

新建 app_can.c/h 文件后进行编写,这里接收部分代码使用了环形缓冲区的方法,详细原理可见:
【双 MCU 架构车载仪表终端(CAN 互联)】STM32F1 CAN 回环收发测试

#include "app_can.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 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;
}

/**
 * @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;
        }
    }
}

3.5 RTOS 回环收发任务创建

创建 FDCAN 任务,以测试 FDCAN 的回环收发:

/**
 * @brief CAN 任务线程
 * @param pvParameters 任务参数指针,本例中未使用
 */
static void FDCAN_Thread(void *pvParameters)
{
    for (;;)
    {
        FDCAN_Transmit(0x123, 8, (uint8_t *)"Hello!");
        osDelay(1000);
        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]);
        }
    }
}

/**
 * @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");
    }
}

4. 串口打印消息

[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] 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] 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

博客导航

博客导航

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