【CAN总线】STM32 的 CAN 总线通信开发笔记(基于 HAL)

发布于:2025-07-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

  1. CAN 总线简介
  2. 波特率计算与误差分析(含 9.735K 与 10K 差异)
  3. 初始化配置详解
  4. CAN 模式说明
  5. CAN 滤波器原理与配置
  6. 消息发送流程
  7. 接收机制(FIFO 与中断)
  8. 源代码与调试技巧

1. ✅ CAN 总线简介

🔸 CAN 是什么?

  • CAN(Controller Area Network) 是一种 多主机、面向消息 的通信协议,最早由德国 Bosch 公司为汽车电子开发,现广泛应用于工业自动化、电梯、医疗、机器人等领域。

🔸 CAN 报文结构:

每条 CAN 消息包含以下主要字段:

字段 含义
ID 报文标识符:决定优先级(ID 越小优先级越高)
DLC Data Length Code:表示数据区长度(0~8 字节)
DATA 有效负载:最多 8 字节的数据

🔸 差分通信原理(CAN_H / CAN_L):

CAN 使用 差分信号 进行通信,有两根线:

线名 正常值 主动发送“显性”时 主动发送“隐性”时
CAN_H ~2.5V 拉高到 ~3.5V 保持 2.5V
CAN_L ~2.5V 拉低到 ~1.5V 保持 2.5V
✅ 特性:
  • 抗干扰能力强:差分信号可抵消共模干扰
  • 无需接地基准:因为只看两线间电压差
  • 远距离通信稳定:典型可达 40 米(1 Mbps)~ 1 km(50 kbps)
显性(Dominant) vs 隐性(Recessive):
状态 CAN_H – CAN_L 差值 作用
显性位(Dominant) ~2V 用于强制总线为 0(主导)
隐性位(Recessive) 0V 表示总线为 1(弱化)

如果两个节点同时发送:一个发“0”(显性),一个发“1”(隐性),最终总线呈现“0” → 实现自动仲裁


🔸 总线拓扑与终端电阻

  • CAN 总线为典型的 双绞线总线拓扑

  • 两端各有一个 120Ω终端电阻,用于:

    • 匹配阻抗,防止反射
    • 稳定电平,增强抗干扰
  • 终端电阻 必须存在,不能省略


2. 📐 波特率计算与误差分析

波特率计算公式:

CAN 波特率=fAPB1Prescaler×(1+BS1+BS2) \text{CAN 波特率} = \frac{f_{\text{APB1}}}{\text{Prescaler} \times (\text{1} + \text{BS1} + \text{BS2})} CAN 波特率=Prescaler×(1+BS1+BS2)fAPB1

示例:

  • APB1 = 48 MHz
  • Prescaler = 3
  • BS1 = 12TQ, BS2 = 2TQ

波特率=48MHz3×(1+12+2)=48MHz45≈1.0667Mbps \text{波特率} = \frac{48 \text{MHz}}{3 \times (1 + 12 + 2)} = \frac{48 \text{MHz}}{45} ≈ 1.0667 \text{Mbps} 波特率=3×(1+12+2)48MHz=4548MHz1.0667Mbps

可以用此公式计算 10Kbps、9.735Kbps 等配置。


📌 重要:波特率偏差与通信不对称问题

实测现象解释:
发送端 接收端 能否通信 原因
9.735K 10K 接收端采样点早,容忍慢速发
10K 9.735K 接收端采样点晚,采不到位变
  • CAN 控制器能容忍一定的波特率误差(一般 ±1.5%~3%)
  • 误差在发送快、接收慢时更容易出错
  • 实际项目中应确保所有节点波特率一致!

3. 🧱 初始化配置详解

初始化在 MX_CAN1_Init() 中完成,关键结构体为:

hcan1.Init.Prescaler = 3;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_12TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;

参数含义:

参数名 说明
Prescaler 分频器,影响总线波特率
Mode 工作模式(NORMAL / LOOPBACK / SILENT)
SyncJumpWidth (SJW) 同步跳转宽度,建议设置为 1
TimeSeg1 比特前段采样点(数据采样位于 Bit 的后1/2~3/4)
TimeSeg2 比特后段,用于同步

建议:保持 (1 + BS1 + BS2) 在 8~25TQ 之间,系统更稳定。


4. ⚙️ 模式(CAN_Mode)—— CAN的运行方式

模式 枚举值 描述 是否发送 是否接收 是否驱动CAN总线 典型用途
正常模式 CAN_MODE_NORMAL 标准工作模式,支持发送与接收 ✅ 是 ✅ 是 ✅ 是 实际通信使用
回环模式 CAN_MODE_LOOPBACK 自发自收,控制器将发送数据直接回收到自身的接收缓冲区,不发送到总线 ✅ 是 ✅ 是 ❌ 否 单机调试、发送逻辑测试
静默模式 CAN_MODE_SILENT 只监听总线上的数据,不主动发送或响应,且不会干扰总线 ❌ 否 ✅ 是 ❌ 否 被动监听,调试总线或与未知设备共存
静默+回环模式 CAN_MODE_SILENT_LOOPBACK 回环 + 静默,完全断开物理总线,仅内部测试 ✅ 是 ✅ 是 ❌ 否 回环测试,不接触外部,适合自动化测试

📝 模式使用建议:

  • 调试阶段:可使用 LOOPBACK 模式验证发送与接收逻辑,确认程序框架正确;
  • 监听设备:例如日志记录器/监控工具,可用 SILENT 模式无干扰地接入总线;
  • 真实通信:正式运行时必须使用 NORMAL 模式才能与其他节点通信;
  • 复杂测试:若需断开物理总线测试通信流程,用 SILENT_LOOPBACK 模式最安全。

❗ 注意事项:

  • LOOPBACKSILENT_LOOPBACK 模式下 不会产生物理电平变化,示波器上看不到 CAN 波形;
  • SILENT 模式下设备不可主动发送,即使调用发送函数也不会生效;
  • 切换为 NORMAL 模式前,必须确保波特率、滤波器、邮箱、FIFO 等配置无误。

5. 🧊 CAN 滤波器(Filter)

📌 目的:

CAN 总线是多主广播机制,所有节点都能看到所有消息。滤波器的作用是:

  • 筛选出感兴趣的消息(指定 ID);
  • 屏蔽无关的数据帧,减轻 MCU 中断负担和内存开销;
  • 支持多个滤波器组(F1xx 系列通常为 14 个);

🧱 配置结构体(CAN_FilterTypeDef

成员 含义 说明
FilterBank 滤波器编号 取值范围 0 ~ N(最多 28 个,取决于芯片)
FilterMode 模式 掩码模式(IDMASK)或列表模式(IDLIST)
FilterScale 尺寸 16位模式 or 32位模式
FilterIdHigh/Low 滤波 ID 高/低 16 位;取决于扩展/标准帧
FilterMaskIdHigh/Low 滤波掩码 与 FilterId 比较的屏蔽位
FilterFIFOAssignment 分配到哪个 FIFO FIFO0 或 FIFO1
FilterActivation 激活开关 启用或禁用该滤波器
SlaveStartFilterBank 用于双 CAN 的分界线 F4/H7 系列特有

🧭 两种模式详解

1️⃣ 掩码模式(CAN_FILTERMODE_IDMASK

作用:按照 ID 的某些位进行匹配(常用于 ID 分段匹配)

  • 匹配规则:

    接收ID & Mask == FilterID & Mask
    
  • 掩码为 0:表示该位“忽略”;

  • 掩码为 1:表示该位“必须精确匹配”;

  • 适合批量匹配某个范围的 ID

示例:接收所有帧(“全开”滤波器)

FilterIdHigh = 0x0000;
FilterMaskIdHigh = 0x0000;  // 所有位都“忽略” → 接收所有帧

示例:只接收 ID 为 0x321 的标准帧

FilterIdHigh = 0x321 << 5;         // 标准 ID 左移 5 位
FilterMaskIdHigh = 0xFFE0;         // 只比较前 11 位

2️⃣ 列表模式(CAN_FILTERMODE_IDLIST

作用:只接收 特定 的几个 ID,不考虑掩码

  • 结构体中 ID 和 Mask 字段被看作 4 个完整 ID 的列表(如果是 16-bit 模式就是 4 个,32-bit 模式就是 2 个);
  • 适合精确接收多个确定 ID(非范围)

示例:只接收 0x100 和 0x200

FilterMode = IDLIST;
FilterIdHigh     = 0x100 << 5;
FilterMaskIdHigh = 0x200 << 5;

📏 滤波器位移说明(标准帧 vs 扩展帧)

  • 标准帧(11位 ID):要左移 5 位后写入 FilterIdHigh
  • 扩展帧(29位 ID):要左移 3 位后拆分成高 16/低 16,写入 FilterIdHigh/Low

🧪 滤波器配置完整示例(接收所有 ID)

CAN_FilterTypeDef canFilter;
canFilter.FilterBank = 0;
canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
canFilter.FilterScale = CAN_FILTERSCALE_32BIT;
canFilter.FilterIdHigh = 0x0000;
canFilter.FilterIdLow  = 0x0000;
canFilter.FilterMaskIdHigh = 0x0000;
canFilter.FilterMaskIdLow  = 0x0000;
canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
canFilter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &canFilter);

📎 FIFO 分配与使用

  • 每个滤波器必须明确分配到 CAN_FILTER_FIFO0FIFO1

  • 接收中断也分 FIFO 注册(HAL_CAN_ActivateNotification()

    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
    

🚦 注意事项

  • 滤波器必须在 HAL_CAN_Start() 之前配置;
  • 若接收不到消息,优先检查滤波器是否太严
  • 同时开启多个滤波器时,FilterBank 编号不可重复;
  • 多路接收时可以将不同的 ID 分配给 FIFO0 / FIFO1,便于分类处理。

6. 📤 消息发送流程

CAN_TxHeaderTypeDef txHeader;
HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &txMailbox);
  • 使用三个邮箱(Tx Mailbox)
  • 可用 HAL_CAN_GetTxMailboxesFreeLevel() 查看空闲邮箱数
  • DLC 为实际发送数据长度

7. 📥 接收逻辑详解(FIFO + 中断)

🔸 什么是 FIFO?

  • FIFO:First In First Out(先进先出)缓冲区

  • STM32 的 CAN 控制器内置两个硬件 FIFO:

    • FIFO0(接收队列0)
    • FIFO1(接收队列1)
  • 每个 FIFO 最多缓存 3 个完整 CAN 帧(包括 ID、DLC、数据等)

  • 当接收到的帧匹配某个滤波器,就被分配到相应 FIFO 中。


🔸 FIFO0 与 FIFO1 的区别?

特性 FIFO0 FIFO1
可使用中断 CAN_IT_RX_FIFO0_MSG_PENDING CAN_IT_RX_FIFO1_MSG_PENDING
可使用回调函数 HAL_CAN_RxFifo0MsgPendingCallback HAL_CAN_RxFifo1MsgPendingCallback
默认选择 默认使用 FIFO0(除非滤波器指定使用 FIFO1)
目的 可以按需分组消息来源、优先级或协议功能,分别放入 FIFO0/1

FIFO0 与 FIFO1 可以同时使用!
可以设置不同的滤波器,将不同 ID 的报文路由到不同的 FIFO,提升任务并行性与可维护性。


🔸 使用中断接收的推荐方式:

  1. 启用中断

    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
    
  2. 实现回调函数(FIFO0 示例):

    void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
    {
        CAN_RxHeaderTypeDef rxHeader;
        uint8_t rxData[8];
    
        HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);
    
        // 处理数据
        printf("Recv ID=0x%X DLC=%d Data=...", rxHeader.StdId, rxHeader.DLC);
    }
    
  3. 好处

    • 不需要轮询接收(节省 CPU 资源)
    • 多个帧自动缓存到 FIFO,防止漏帧
    • 响应及时可靠

🔸 所有 CAN 中断说明:

STM32 中与 CAN 相关的 IRQ 一共四种,使用时建议根据用途精细启用:

中断向量名称 描述 用途 是否必须
CAN1_TX_IRQn 发送邮箱中断 发送完成 / 可用时触发 可选,若需处理发送确认
CAN1_RX0_IRQn FIFO0 接收中断 接收到帧(匹配滤波器并进入 FIFO0) ✅ 推荐开启
CAN1_RX1_IRQn FIFO1 接收中断 同上,针对 FIFO1 如使用 FIFO1,则必须
CAN1_SCE_IRQn 状态变化与错误中断(SCE) 错误帧、仲裁失败、总线关闭等 可选,适合诊断问题

若只用 FIFO0 接收,可以仅启用 CAN1_RX0_IRQnCAN_IT_RX_FIFO0_MSG_PENDING。其余中断可用 HAL_NVIC_DisableIRQ(...) 关闭,以节省资源。


🔸 HAL 中断控制常用函数:

函数 功能
HAL_NVIC_EnableIRQ(IRQn_Type IRQn) 启用指定中断向量
HAL_NVIC_DisableIRQ(IRQn_Type IRQn) 禁用指定中断
__HAL_CAN_ENABLE_IT() 启用 CAN 中的特定中断标志位(如 RX、TX)
__HAL_CAN_CLEAR_FLAG() 清除中断标志

✅ 示例配置(启用 FIFO0 接收中断):

HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);

如果想使用 FIFO1 接收,记得:

  • 将对应滤波器的 FilterFIFOAssignment 设置为 CAN_FILTER_FIFO1
  • 同时使能 CAN_IT_RX_FIFO1_MSG_PENDING
  • 实现 HAL_CAN_RxFifo1MsgPendingCallback(...)

8. 🛠️ 源码与调试技巧

can.c:

/* USER CODE BEGIN Header */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "can.h"

/* USER CODE BEGIN 0 */
#include "stdio.h"  // printf用
void CAN1_Start(void);
void CAN1_ConfigFilter(void);
/* USER CODE END 0 */

CAN_HandleTypeDef hcan1;

/* CAN1 init function */
void MX_CAN1_Init(void)
{

  /* USER CODE BEGIN CAN1_Init 0 */
  /* USER CODE END CAN1_Init 0 */

  /* USER CODE BEGIN CAN1_Init 1 */
  /* USER CODE END CAN1_Init 1 */
  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 3;
  hcan1.Init.Mode = CAN_MODE_NORMAL;
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan1.Init.TimeSeg1 = CAN_BS1_12TQ;
  hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
  hcan1.Init.TimeTriggeredMode = DISABLE;
  hcan1.Init.AutoBusOff = ENABLE;
  hcan1.Init.AutoWakeUp = DISABLE;
  hcan1.Init.AutoRetransmission = ENABLE;
  hcan1.Init.ReceiveFifoLocked = DISABLE;
  hcan1.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN1_Init 2 */
  CAN1_ConfigFilter();
  CAN1_Start();
  /* USER CODE END CAN1_Init 2 */

}

void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspInit 0 */
  /* USER CODE END CAN1_MspInit 0 */
    /* CAN1 clock enable */
    __HAL_RCC_CAN1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**CAN1 GPIO Configuration
    PA11     ------> CAN1_RX
    PA12     ------> CAN1_TX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* CAN1 interrupt Init */
    HAL_NVIC_SetPriority(CAN1_TX_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(CAN1_TX_IRQn);
    HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
    HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
    HAL_NVIC_SetPriority(CAN1_SCE_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(CAN1_SCE_IRQn);
  /* USER CODE BEGIN CAN1_MspInit 1 */
  /* USER CODE END CAN1_MspInit 1 */
  }
}

void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{

  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspDeInit 0 */
  /* USER CODE END CAN1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_CAN1_CLK_DISABLE();

    /**CAN1 GPIO Configuration
    PA11     ------> CAN1_RX
    PA12     ------> CAN1_TX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);

    /* CAN1 interrupt Deinit */
    HAL_NVIC_DisableIRQ(CAN1_TX_IRQn);
    HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);
    HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn);
    HAL_NVIC_DisableIRQ(CAN1_SCE_IRQn);
  /* USER CODE BEGIN CAN1_MspDeInit 1 */
  /* USER CODE END CAN1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/**
 * @brief 配置 CAN1 滤波器(接收所有标准帧)
 */
void CAN1_ConfigFilter(void)
{
    CAN_FilterTypeDef canFilter;

    canFilter.FilterBank = 0;
    canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
    canFilter.FilterScale = CAN_FILTERSCALE_32BIT;

    canFilter.FilterIdHigh     = 0x0000;
    canFilter.FilterIdLow      = 0x0000;
    canFilter.FilterMaskIdHigh = 0x0000;
    canFilter.FilterMaskIdLow  = 0x0000;

    canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;
    canFilter.FilterActivation = ENABLE;
    canFilter.SlaveStartFilterBank = 14;

    if (HAL_CAN_ConfigFilter(&hcan1, &canFilter) != HAL_OK)
    {
        Error_Handler();
    }
}
// /**
//  * @brief 配置 CAN1 滤波器(ID范围0x100~0x10F)
//  */
// void CAN1_ConfigFilter(void)
// {
//     CAN_FilterTypeDef canFilter = {0};

//     canFilter.FilterBank = 0;
//     canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
//     canFilter.FilterScale = CAN_FILTERSCALE_32BIT;

//     // 过滤ID范围0x100~0x10F,掩码屏蔽最低4位
//     uint32_t filter_id = 0x100 << 21;
//     uint32_t filter_mask = 0x7F0 << 21;  // 掩码最低4位不比较,其他位比较

//     canFilter.FilterIdHigh     = (filter_id >> 16) & 0xFFFF;
//     canFilter.FilterIdLow      = filter_id & 0xFFFF;
//     canFilter.FilterMaskIdHigh = (filter_mask >> 16) & 0xFFFF;
//     canFilter.FilterMaskIdLow  = filter_mask & 0xFFFF;

//     canFilter.FilterFIFOAssignment = CAN_RX_FIFO0;
//     canFilter.FilterActivation = ENABLE;
//     canFilter.SlaveStartFilterBank = 14;

//     if (HAL_CAN_ConfigFilter(&hcan1, &canFilter) != HAL_OK)
//     {
//         printf("Filter config failed!\r\n");
//         Error_Handler();
//     }
// }

/**
 * @brief 启动 CAN1 并启用接收中断
 */
void CAN1_Start(void)
{
    if (HAL_CAN_Start(&hcan1) != HAL_OK)
    {
        printf("CAN Start Failed!\r\n");
        Error_Handler();
    }
    else
    {
        printf("CAN Started Successfully.\r\n");
    }

    if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
    {
        printf("Enable RX Notification Failed!\r\n");
        Error_Handler();
    }
    else
    {
        printf("RX Notification Enabled.\r\n");
    }
}

/**
 * @brief CAN1 发送一帧标准数据帧
 * @param StdId  标准帧ID
 * @param data   数据指针(最多8字节)
 * @param len    数据长度(0~8)
 */
HAL_StatusTypeDef CAN1_SendMessage(uint32_t StdId, uint8_t *data, uint8_t len)
{
    CAN_TxHeaderTypeDef txHeader;
    uint32_t txMailbox;

    txHeader.StdId = StdId;
    txHeader.ExtId = 0;
    txHeader.IDE = CAN_ID_STD;
    txHeader.RTR = CAN_RTR_DATA;
    txHeader.DLC = len;
    txHeader.TransmitGlobalTime = DISABLE;

    if (HAL_CAN_AddTxMessage(&hcan1, &txHeader, data, &txMailbox) != HAL_OK)
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

/**
 * @brief 接收中断回调函数(FIFO0)
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    if (hcan->Instance == CAN1)
    {
        CAN_RxHeaderTypeDef rxHeader;
        uint8_t rxData[8];

        if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK)
        {
            printf("CAN1 RX: ID=0x%03X DLC=%d Data=", rxHeader.StdId, rxHeader.DLC);
            for (int i = 0; i < rxHeader.DLC; i++)
            {
                printf("%02X ", rxData[i]);
            }
            printf("\r\n");
        }
    }
}

/**
 * @brief 示例函数:仅发送一帧
 */
void Example_CAN_Work(void)
{
    uint8_t exampleData[8] = {0x19, 0x92, 0x93, 0x74, 0x55, 0x36, 0x77, 0x00};

    if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) > 0)
    {
        if (CAN1_SendMessage(0x123, exampleData, 8) == HAL_OK)
        {
            printf("CAN message sent.\r\n");
        }
        else
        {
            printf("CAN send error.\r\n");
        }
    }
    else
    {
        printf("No free CAN Tx mailbox!\r\n");
    }
}

/* USER CODE END 1 */

避免问题:

  • 确认终端电阻(120Ω × 2)
  • 检查波特率一致性
  • 使用 CAN分析仪 做抓包验证
  • 若收不到数据,先试试是否 开启中断 + 滤波器未屏蔽

✅ 代码启动顺序建议

MX_CAN1_Init();       // 初始化
CAN1_ConfigFilter();  // 设置滤波器(可接收哪些帧)
CAN1_Start();         // 启动 + 启用接收中断

✅ 示例发送(发送一帧):

uint8_t data[8] = {0x01, 0x02, 0x03};
CAN1_SendMessage(0x123, data, 3);

✅ 示例接收(中断自动触发)

void HAL_CAN_RxFifo0MsgPendingCallback(...)
{
    HAL_CAN_GetRxMessage(...);
    // 打印数据
}

🔚 总结

CAN 的收发流程并不复杂,难点在于波特率配置、滤波器使用、同步机制的理解。
亲测的 9.735kbps 与 10kbps 通信失败,正体现了接收采样点延迟带来的不对称性问题,是工程上值得注意的重要细节。


网站公告

今日签到

点亮在社区的每一天
去签到