【嵌入式】CAN通信

CAN 总线最初由博世于1980年代为汽车行业开发,能够简化复杂的布线网络,还确保可靠和安全的数据传输。

1.CAN技术解释

CAN网络中的每个节点,都是平等的,没有主次之分,这一点和SPI和I2C不同。每个节点都可以在需要的时候收发数据,同时也在监听来自其他节点的数据传输。CAN还具有如下优势:

  • 降低网络复杂性:单个 CAN 网络可以取代多条通信线路,从而降低复杂性和成本;
  • 抗干扰性强:CAN 总线采用抗电磁干扰设计,即使在高干扰环境中也能确保可靠和稳定的通信。

1.1 CAN总线的拓扑物理结构

CAN分为高速和低速,如下图所示:
在这里插入图片描述
CAN总线拓扑结构就像一个地铁,每个站点就是节点。每个节点可以和线路上的其他节点进行通信,有如下物理特点:
双绞线结构
成对的电线相互缠绕,可最大限度地减少电磁干扰。想象一下这样一个场景,CAN 电缆安装在电源附近,例如车辆中的交流发电机,这会产生明显的电磁噪声。通常这种噪声可能会干扰和扭曲通信信号。然而,CAN 总线在 CAN 高电平和 CAN 低电平线路之间使用了巧妙的差分信号系统。这意味着任何影响一条线路的干扰信号也会以类似的方式影响另一条线路,从而允许系统相互抵消。因此,即使存在大量噪声,传输的数据也能保持清晰且未损坏。此特性与以太网等系统相同。

CAN High和CAN Low线
根据ISO 11898(高速CAN)和ISO 11519(低速CAN)标准,CAN_H和CAN_L的电压和显/隐性(逻辑0和逻辑1)是不一样的。

类型状态CAN_H电压范围CAN_L电压范围差分电压传输速率
高速CAN显性(逻辑0)2.75V ~ 4.5V0.5V ~ 2.75V≥1.5V1Mbps
隐性(逻辑1)2.0V ~ 3.0V2.0V ~ 3.0V≈0V (±0.05V)
低速CAN显性(逻辑0)≥3.0V≤2.0V≥1.5V10-125Kbps
隐性 (逻辑1)≤1.0V≥4.0V≤-2.0V

1.2 CAN总线终端电阻作用

高速CAN总线需在物理两端各接一个120Ω电阻,形成总电阻60Ω的网络,具有如下作用:

  1. 提高抗干扰能力。若无终端电阻,隐性无压差时,外部很小的干扰就可能导致进入显性。
  2. 快速进入隐性状态。加个终端电阻,加速寄生电容放电。
  3. 提高信号质量。

一、提高抗干扰能力
CAN总线有“显性”和“隐性”两种状态,“显性”有压差,“隐性”无压差,由CAN收发器决定。下图是一个CAN收发器的典型内部结构图,CANH、CANL连接总线。
在这里插入图片描述
总线显性时,收发器内部Q1、Q2导通,CANH、CANL之间产生压差;隐性时,Q1、Q2截止,CANH、CANL处于无源状态,压差为0。
总线若无负载,隐性时差分电阻阻值很大,内部的MOS管属于高阻态,外部的干扰只需要极小的能量即可令总线进入显性(一般的收发器显性门限最小电压仅500mV)。这个时候如果有差模干扰过来,总线上就会有明显的波动,而这些波动没有地方能够吸收掉他们,就会在总线上创造一个显性位出来。所以为提升总线隐性时的抗干扰能力,可以增加一个差分负载电阻,且阻值尽可能小,以杜绝大部分噪声能量的影响。然而,为了避免需要过大的电流总线才能进入显性,阻值也不能过小。

二、快速进入隐性状态
加电阻前,可以看到由高变低时,不够快:
在这里插入图片描述

加上终端电阻:
在这里插入图片描述
三、提高信号质量
高频信号在传输线末端会因阻抗突变产生反射波,反射与原始信号叠加形成振铃(信号振荡),导致电平失真。终端电阻通过匹配传输线特征阻抗(约120Ω),吸收反射能量,避免波形畸变。
带振铃:
在这里插入图片描述
加上电阻后:
在这里插入图片描述

为何120Ω?

什么是阻抗?在电学中,常把对电路中电流所起的阻碍作用叫做阻抗。阻抗单位为欧姆,常用Z表示,是一个复数Z= R+i( ωL–1/(ωC))。具体说来阻抗可分为两个部分,电阻(实部)和电抗(虚部)。其中电抗又包括容抗和感抗,由电容引起的电流阻碍称为容抗,由电感引起的电流阻碍称为感抗。这里的阻抗是指Z的模。

任何一根线缆的特征阻抗都可以通过实验的方式得出。线缆的一端接方波发生器,另一端接一个可调电阻,并通过示波器观察电阻上的波形。调整电阻阻值的大小,直到电阻上的信号是一个良好的无振铃的方波,此时的电阻值可以认为与线缆的特征阻抗一致。
采用两根汽车使用的典型线缆,将它们扭制成双绞线,就可根据上述方法得到特征阻抗大约为120Ω,这也是CAN标准推荐的终端电阻阻值,所以这个120Ω是测出来的,不是算出来的,都是根据实际的线束特性进行计算得到的。当然在ISO 11898-2这个标准里面也是有定义的。

不接120Ω后果?

不接终端电阻的后果

  • 通信故障,信号反射导致电平波动,可能误触发显性位,引发CRC校验错误数据帧丢失
  • 波形异常,示波器观测显示信号下降沿变缓、振铃明显,隐性状态恢复时间延长,影响高速通信(如CAN FD)的时序容限
  • 抗干扰能力下降,隐性状态易受外部噪声干扰,增加误码率。

为什么功率还要选0.25W?

这个就要结合一些故障状态也计算,汽车ECU的所有接口都需要考虑短路到电源和短路到地的情况,所以我们也需要考虑CAN总线的节点短路到电源的情况,根据标准需要考虑短路到18V的情况,假设CANH短路到18V,电流会通过终端电阻流到CANL上,而CANL内部由于限流的原因,最大注入电流为50mA(TJA1145的规格书上标注),这时候120Ω电阻的功率就是50mA50mA120Ω=0.3W。考虑到高温情况下的降额,终端电阻的功率就是0.5W。

1.3 CAN帧结构

总的来说CAN协议帧有5种类型:
在这里插入图片描述
整体的结构如下:
在这里插入图片描述

  • 帧开始 (SOF): 单bit表示帧开始。
  • 仲裁域(Arbitration):包括确定帧优先级的消息标识符 (ID) 和远程请求的远程传输请求 (RTR) 位。
  • 控制域 :包括 DLC(数据长度代码),指示数据的字节数。
  • 数据字段:包含帧的实际数据,最多 8 个字节。
  • CRC字段 :用于错误检查,包括 CRC 序列和 CRC 分隔符。
  • ACK: 包括确认位和确认分隔符。
  • EOF: 由 7 个隐性位(1)组成,标记帧的结束。
  • 间歇场:帧间隔专用,由3个隐性位组成。
  • 用于错误或过载的其他字段

1.3.1 数据帧 - 标准帧(11位ID)

在这里插入图片描述

在这里插入图片描述

1.3.2 数据帧 - 扩展帧(29位ID)

在这里插入图片描述

在这里插入图片描述

1.3.2 扩展帧与标准帧使用场景

使用场景ID类型ID设计思路
简单网络标准帧预留固定ID段给不同节点,如0x100-0x1FF
复杂网络扩展帧设计ID高位为设备类型/功能码,低位为节点编号
多厂家系统扩展帧高位区分厂家ID,低位区分设备和功能
广播与点对点混合标准或扩展帧广播使用固定ID的标准帧,点对点用不同ID的扩展帧

1.3.3 实际场景举例

比如有多个板卡,每个板卡带有板卡类型+板卡ID,他们之间通过CAN总线连接到一起,板卡可以进行广播发送和点对点发送,此时CAN帧消息结构可以这样定义:
标准帧用来发送广播消息,一个板子可以给其他板子广播发送数据。消息类别中可以用来区分消息的优先级,可以支持4种优先级、16种板卡类型和32个板子互联:
在这里插入图片描述
点对点使用扩展帧,ID的前11bit和标准帧一样,但后18bit中可以描述目标板卡的信息:
在这里插入图片描述
这样设计很方便的进行消息的优先级控制以及板卡的消息过滤功能,比如只过滤指定板卡类型的消息,甚至是特定的消息!

1.4 总线仲裁机制

当多个节点可能同时尝试发送数据时,需要一种机制,保证总线上只有一个节点能继续发送,其他节点延迟发送。
仲裁保证高优先级消息优先传输,且总线无碰撞。
【核心原理】

  • 基于CAN报文的ID字段进行仲裁。 CAN总线采用差分信号,逻辑“0”(Dominant)比逻辑“1”(Recessive)电平优先。即ID数值越小,优先级越高(ID低 = 优先级高)
  • 节点在发送ID位时,同时监听总线上的实际电平。
  • 如果节点发送“1”,但读总线实际是“0”,说明有高优先级节点发送“0”,当前节点立即停止发送(放弃仲裁)。如果发送1,读总线是1,继续发送。直到只剩下一个节点继续发送,该节点获得总线控制权。
  • CAN控制器自动完成仲裁,无需软件干预

在这里插入图片描述

1.5 总线标识符ID过滤

CAN 总线使用独特的过滤机制来处理消息。这种方法增强了其效率和灵活性。过滤在多个节点同时通信的复杂系统中特别有用。它确保重要信息到达适当的接收者,而不会让其他节点因非必要数据而变得混乱。

正如前面所述,可以配置过滤器,指定匹配ID中的部分字段,比如该字段可能描述目标设备ID,实现当目标设备ID配置为自身时,只接收发往自己的单点通信的功能。

基本概念:

  • 滤波器(Filter):硬件模块,负责检测报文ID是否匹配。
  • 掩码(Mask):决定ID中哪些位参与匹配,哪些位忽略。
  • FIFO0 和 FIFO1:两个独立的接收FIFO,过滤后的报文存放位置。

【注意】CAN控制器的滤波器硬件设计只支持对ID的匹配,不支持根据数据位内容过滤。

两种模式:掩码模式和列表模式。

  • 掩码模式
    过滤器ID与掩码组合,匹配过滤器ID中掩码指定的位。即报文ID 与滤波器ID 按位进行比较,掩码为1的位置必须匹配,掩码为0的位置忽略。
    公式如下:
(Received_ID & Mask) == (Filter_ID & Mask)

例如,掩码为0x7FF(0111 1111 1111),过滤器ID为0x100(0001 0000 0000 ),只能匹配0x100;
掩码为0x700(0111 0000 0000),过滤器ID为0x100(0001 0000 0000 ),对应上后,001和掩码匹配上了,只能匹配(001 0000 0000 ~ 001 1111 1111)即0x100~0x1FF。

  • 列表模式
    最简单,就是和指定的ID直接匹配。

1.6 CAN FD

CAN FD(CAN with Flexible Data-Rate) 是 CAN 总线协议的增强版本,由 Bosch 公司在 2012 年提出,意在突破传统 CAN 总线的数据传输速率和数据长度限制。

特性传统 CANCAN FD
最大数据长度8 字节最多 64 字节
数据传输速率通常最高 1 Mbps数据段最高可达 8 Mbps(甚至更高)
传输效率较低(受限于帧长和速率)更高,支持更大数据量和更高速率
兼容性传统设备兼容传统 CAN,需支持 FD 的设备

STM32F7、STM32H7等部分型号MCU内置CAN FD模块,适合嵌入式开发。
Jetson AGX Orin支持CAN FD。

2.STM32中的CAN通信

2.1 CAN外设介绍

2.1.1 CAN 框图

在这里插入图片描述
CAN2的(只有互联型设备才有CAN2)。
本次使用的STM32F103C8T6只有CAN1。
简单看一下,发送的数据会进入3个发送邮箱进行数据发送,接收的数据首先进过滤器,然后放到FIFO中,每个FIFO有3个接收邮箱。

2.1.2 发送过程

发送的数据通过CPU写入到邮箱中,然后给出请求发送的命令,然后发送和接收控制器就会等待总线空闲,然后自动把这个报文广播到总线上。

为何需要3个邮箱?
简单来说就是3级缓存,减少发送的CPU等待。如果全满了那就没办法了。

当两个或者三个邮箱都有数据要发送,那么会先发哪一个呢?手册中是这么写的:
在这里插入图片描述

实际上,如果 hcan.Init.TransmitFifoPrioritys (即MX中Transmit Fifo Priority选项)设置为了Enable,表示第二种方法,按发送请求次序确定。
如果是Disable,则按消息的ID优先级来确认,当ID相同时,按邮箱号确定。

2.1.3 接收过程

接收到的报文,首先会进入过滤器,根据过滤规则,被存储在过滤器指定的3级邮箱深度的FIFO中(即过滤器绑定的FIFIO0还是FIFO1中。一个过滤器都没有则会进入FIFO0。)。FIFO完全由硬件来管理,从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文。

FIFO的3个邮箱都是满的,再收到一个数据会怎样?

  • 如果禁用了FIFO锁定功能(CAN_MCR寄存器的RFLM位被清’0’),那么FIFO中最后收到的报文就被新报文所覆盖。这样,最新收到的报文不会被丢弃掉(但实际上被覆盖的报文是丢掉的),此时发送端应该不会重传。对应ReceiveFifoLocked 为DISABLE;
  • 如果启用了FIFO锁定功能(CAN_MCR寄存器的RFLM位被置’1’),那么新收到的报文就被丢弃,软件可以读到FIFO中最早收到的3个报文。此时发送端应该会重传吧(不确定)。对应ReceiveFifoLocked 为ENABLE。

接收相关的中断

  • 一旦往FIFO存入一个报文,会产生一个中断请求。
  • 当FIFO变满时(即第3个报文被存入),会产生一个满中断请求。
  • 在溢出的情况下,会产生一个溢出中断请求。

2.2 MX配置

基本配置:
在这里插入图片描述

  • 波特率计算
    Prescaler Time Quanta in Bit Segment 1和2是为了计算波特率使用的。
    CAN挂在APB1总线上,这里配置的是36MHz。
    位段1(BS1,Bit Segment 1):采样点之前的时间段
    位段2(BS2,Bit Segment 2):采样点之后的时间段
    最终的波特率是下面公式计算出来的:
36M/分频系数/(BS1 + BS2 + 1)

我们目标是配置出1Mbit/s的速率,那么可以使用小工具进行计算,CAN波特率计算工具:

在这里插入图片描述

gpt得知,采样点在75%-87.5% 左右(靠近比特末尾)可确保数据稳定,这里选择了BS1:14 BS2:3这一条。

  • Basic Parameters(基本参数)

    • Automatic Retransmission 是否允许自动重传错误帧,建议开启
    • Transmit Fifo Priority 发送 FIFO 优先级模式,启用后按 FIFO 顺序发送,禁用则按报文标识符优先级发送。建议启用,保证发送的顺序。
    • Receive Fifo Locked 设置为ENABLE,接收FIFO满了,会将报文丢弃(对端应该会重传吧)。建议启用。
  • Test Mode
    有四种选择:
    在这里插入图片描述

    • Normal 正常模式
      生产环境或实际应用中的标准工作模式,多机通信。
    • Loop Back 环回模式
      软件调试,验证发送与接收功能。CAN 控制器内部环回发送和接收数据,不发送到总线。在环回模式下,bxCAN在内部把Tx输出回馈到Rx输入上,而完全忽略CANRX引脚的实际状态。在环回模式下CAN内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位),但会进过滤器。
    • Silent 静默模式
      控制器只接收总线数据,但不发送任何信号或应答。监听模式,用于被动监听总线数据。
    • Silent Loop Back 静默环回模式
      软件调试,验证接收功能,无干扰总线。结合环回和静默,内部环回数据,不送出总线,也不应答

中断配置:
创建代码:

  1. CAN1 TX interrupts
    触发条件:发送完成中断
    用途:确认报文已成功发送,释放发送缓冲区,可以继续发送新的数据。
    应用:在中断服务函数中通常清标志位,通知上层发送完成。
  2. CAN1 RX0 interrupts
    触发条件:接收 FIFO 0 有新报文到达。
    用途:读取 FIFO 0 中的 CAN 报文,进行数据处理。
    特点:FIFO 0 常用于普通消息接收,响应速度较快。
  3. CAN1 RX1 interrupt
    触发条件:接收 FIFO 1 有新报文到达。
    用途:读取 FIFO 1 中的 CAN 报文,适合区分不同消息队列。
    特点:可以用来区分高优先级和低优先级消息,或不同来源的消息。
  4. CAN1 SCE interrupt
    触发条件:CAN 状态变化或错误事件。
    包含事件:
    错误计数器变化(Error Passive / Active 状态)
    总线错误(Bit Error、Stuff Error 等)
    总线关闭(Bus-Off)
    唤醒事件
    用途:监控 CAN 总线健康状态,及时响应和处理错误,保证通信可靠。

【创建代码】
创建了can.c,其中的初始化:

void MX_CAN_Init(void)
{hcan.Instance = CAN1;hcan.Init.Prescaler = 2;hcan.Init.Mode = CAN_MODE_NORMAL;hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_14TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = DISABLE;hcan.Init.AutoRetransmission = ENABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = ENABLE;if (HAL_CAN_Init(&hcan) != HAL_OK){Error_Handler();}
}

2.3 过滤器详解

接收到的数据,首先会经过过滤器,符合条件的才会进入FIFO。
还得掏出下面这个图:
中文版:
在这里插入图片描述
英文版:
在这里插入图片描述
【这里推荐英文版的,中文版的过滤器编号和过滤器组把人搞懵逼】 中文版中的过滤器编号并不和程序中的FilterBank字段对应,和FilterBank字段对应的中文版中是过滤器组!

简单看一下,前面提到了,过滤器有两种模式:掩码纯ID,这张图中,按照FilterScale的不同,还得分16位还是32位,所有总共有4种寄存器配置方式:

  • 1个32位的针对标识符屏蔽过滤器(即掩码模式)
  • 2个32位的只针对标识符的过滤器(即纯ID模式),也就是你只能配置2个ID列表方式的过滤器,只能过滤2个ID号
  • 2个16位的针对标识符屏蔽过滤器(即掩码模式)
  • 4个32位的只针对标识符的过滤器(即纯ID模式)

**【注意-易错点】**这里的1个、2个和4个针对的是一个过滤器编号中对应了几个。比如当使用HAL_CAN_ConfigFilter设置一个过滤器时,可以给它配置2个32为列表标识的过滤器。这样推断,STM32F1X只有一个CAN,一共可配置14个过滤器,每个过滤器可以按16位还是32位进行 1个、2个和4个这样的配置!

2.3.1 32位掩码模式的过滤器配置

注意STF1/4/7都支持32bit配置!

在这里插入图片描述
这里的Mapping实际就是我们需要配置的位。为了简化配置,我们可以提炼为一个结构体,可支持对掩码和ID的配置:

typedef struct CanFilterCfg
{uint32_t b1Reserve   : 1; //最低位未用   uint32_t b1RTR       : 1; //RTR过滤位    uint32_t b1IDE       : 1; //扩展/标准帧标识 可用来过滤扩展帧还标准帧  uint32_t b18EXIDL    : 18;//扩展帧ID,可以根据实际业务再进行拆分uint32_t b11STID   	 : 11;//标准帧ID,可以根据实际业务再进行拆分
}TCanFilterCfg; //正好32位typedef union CanFilterDesc
{TCanFilterCfg Cfg;uint32_t Val;
}UCanFilterDesc;

比如只过滤标准帧:

	CAN_FilterTypeDef  sFilterConfig;UCanFilterDesc uFilterMask;   //过滤掩码选定UCanFilterDesc uFilterId;     //过滤目标选定//掩码,只对扩展帧/标准帧b1IDE进行标记,这里对只关心的字段按bit赋值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //实际ID,只设置标准帧标记uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //标准帧,1是扩展帧sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //32位sFilterConfig.FilterIdHigh = ((uFilterId.Val >> 16) & 0x0000FFFF); //高16bitsFilterConfig.FilterIdLow =  (uFilterId.Val  & 0x0000FFFF);sFilterConfig.FilterMaskIdHigh = ((uFilterMask.Val >> 16) & 0x0000FFFF);sFilterConfig.FilterMaskIdLow = (uFilterMask.Val  & 0x0000FFFF);sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;sFilterConfig.SlaveStartFilterBank = 14; //固定配置,指定第二个CAN的Bank从哪个编号开始if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK){return -1;}

2.3.2 16位掩码模式的过滤器配置(踩坑)

在这里插入图片描述
和程序中配置字段的对应关系:
在这里插入图片描述

同样的,Mapping抽象为一个位域结构。不同的是,因为是16位的,一个过滤器Bank可对应两个,FilterIdHigh对应一个,FilterIdLow 对应另一个。

typedef struct CanFilterCfg
{uint32_t b3EXID      : 3; //扩展帧ID的高[17:15]位  uint32_t b1IDE       : 1; //扩展/标准帧标识 可用来过滤扩展帧还标准帧      uint32_t b1RTR       : 1; //RTR过滤位    uint32_t b11STID   	 : 11;//标准帧ID,可以根据实际业务再进行拆分
}TCanFilterCfg16; typedef union CanFilterDesc
{TCanFilterCfg16 Cfg;uint32_t Val;
}UCanFilterDesc16;

这里我们也是只过滤标准帧,扩展帧收不到了:

//CAN初始化过滤器设置
int CAN_Init()
{//过滤器配置 - 只接收标准帧CAN_FilterTypeDef  sFilterConfig;UCanFilterDesc16 uFilterMask;   //过滤掩码选定UCanFilterDesc16 uFilterId;     //过滤目标选定//掩码,只对扩展帧/标准帧b1IDE进行标记,这里对只关心的字段按bit赋值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //实际ID,只设置标准帧标记uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //标准帧sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; sFilterConfig.FilterIdHigh = uFilterId.Val;  //注意这里直接赋值即可sFilterConfig.FilterIdLow = uFilterId.Val //0; 注意千万别配置为0sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = uFilterMask.Val;//0;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;//sFilterConfig.SlaveStartFilterBank = 14; //F1中这个配置也无效if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK){return -1;}return 0;
}

【巨坑】
下面这段代码一开始,Low都赋值为0,结果发现过滤器根本不起作用。这里还是自己理解有误,手册说的也不清楚。
像16bit的配置模式,对应两个过滤器,这其中有一个为0的话,实际上是什么都不过滤的!
High和Low配置成一样的就好了!
建议直接用32bit的!

	sFilterConfig.FilterIdHigh = uFilterId.Val;  //注意这里直接赋值即可sFilterConfig.FilterIdLow = uFilterId.Val //0; 注意千万别配置为0sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = uFilterMask.Val;//0;

2.4 回环模式测试

mx配置中,Test Mode配置为lookback模式,进行单机测试。

初始化时,启用中断和CAN

	//CAN初始化设置int CAN_Init(){//过滤器配置 - 只接收标准帧CAN_FilterTypeDef  sFilterConfig;UCanFilterDesc16 uFilterMask;   //过滤掩码选定UCanFilterDesc16 uFilterId;     //过滤目标选定//掩码,只对扩展帧/标准帧b1IDE进行标记,这里对只关心的字段按bit赋值1即可uFilterMask.Val = 0; //初始化uFilterMask.Cfg.b1IDE = 1; //实际ID,只设置标准帧标记uFilterId.Val = 0;uFilterId.Cfg.b1IDE = 0; //标准帧sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; sFilterConfig.FilterIdHigh = uFilterId.Val; sFilterConfig.FilterIdLow =  0; //这个不配置了sFilterConfig.FilterMaskIdHigh = uFilterMask.Val;sFilterConfig.FilterMaskIdLow = 0;sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;sFilterConfig.FilterActivation = ENABLE;//sFilterConfig.SlaveStartFilterBank = 14; //F1中这个配置也无效if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK){return -1;}//激活接收中断 FIFO0中只要收到消息就进中断if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){return -1;}//使能发送MailBox  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK){return -1;}//使能CAN节点收发if (HAL_CAN_Start(&hcan) != HAL_OK){return -1;}return 0;
}

2.4.1 各种中断

/* Transmit Interrupt /
#define CAN_IT_TX_MAILBOX_EMPTY ((uint32_t)CAN_IER_TMEIE) /
!< 用来驱动发送流程,比如发送完成后通知程序准备加载下一帧数据。必须确保发送邮箱确实空后才写数据,否则可能导致数据覆盖。*/

/* Receive Interrupts /
#define CAN_IT_RX_FIFO0_MSG_PENDING ((uint32_t)CAN_IER_FMPIE0) /
!< FIFO 0 中有新的消息进入且等待处理时触发。 中断服务程序要尽快读取消息,防止 FIFO 堆积导致溢出。/
#define CAN_IT_RX_FIFO0_FULL ((uint32_t)CAN_IER_FFIE0) /
!<FIFO 0 缓冲区满,无法再接收新消息时触发。 /
#define CAN_IT_RX_FIFO0_OVERRUN ((uint32_t)CAN_IER_FOVIE0) /
!< FIFO 0 出现溢出,丢失一条或多条消息时触发。 /
#define CAN_IT_RX_FIFO1_MSG_PENDING ((uint32_t)CAN_IER_FMPIE1) /
!< FIFO 1 message pending interrupt /
#define CAN_IT_RX_FIFO1_FULL ((uint32_t)CAN_IER_FFIE1) /
!< FIFO 1 full interrupt /
#define CAN_IT_RX_FIFO1_OVERRUN ((uint32_t)CAN_IER_FOVIE1) /
!< FIFO 1 overrun interrupt */

/* Operating Mode Interrupts /
#define CAN_IT_WAKEUP ((uint32_t)CAN_IER_WKUIE) /
!< CAN 控制器从休眠模式(sleep)被唤醒时触发。适用于低功耗应用,确保唤醒后重新初始化通信。 /
#define CAN_IT_SLEEP_ACK ((uint32_t)CAN_IER_SLKIE) /
!< CAN 控制器进入休眠模式时触发,表示睡眠模式已被确认。确认进入低功耗状态,系统可进入节能状态。Sleep acknowledge interrupt */

/* Error Interrupts /
#define CAN_IT_ERROR_WARNING ((uint32_t)CAN_IER_EWGIE) /
!< CAN 控制器检测到错误计数器达到警告阈值(如接收或发送错误计数器超过某值)。 提示总线错误增加,需关注通信质量。 /
#define CAN_IT_ERROR_PASSIVE ((uint32_t)CAN_IER_EPVIE) /
!< CAN 控制器进入错误被动状态,即总线错误严重,但尚未完全失效。 /
#define CAN_IT_BUSOFF ((uint32_t)CAN_IER_BOFIE) /
!< CAN 控制器进入总线关闭状态,因错误计数器超过阈值,停止总线通信。 /
#define CAN_IT_LAST_ERROR_CODE ((uint32_t)CAN_IER_LECIE) /
!< 总线错误发生时触发,记录最后一次错误代码。 /
#define CAN_IT_ERR OR ((uint32_t)CAN_IER_ERRIE) /
!< 总线出现任意错误时触发。 需要结合错误状态寄存器分析具体错误类型。*/

2.4.2 数据发送 - 中断方式

需使能中断:

	//使能发送MailBox  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK){return -1;}

发送测试消息:

	//CAN初始化CAN_Init();uint8_t u8Data[8] = {1,2,3,4,5,6,7,8};//发送uint8_t u8TxBoxesNum = HAL_CAN_GetTxMailboxesFreeLevel(&hcan); //空闲发送邮箱的个数if (u8TxBoxesNum > 0){CAN_Send_STD_Frame(u8Data); //发送标准帧}u8TxBoxesNum = HAL_CAN_GetTxMailboxesFreeLevel(&hcan); //空闲发送邮箱的个数if (u8TxBoxesNum > 0){CAN_Send_EXT_Frame(u8Data); //发送扩展帧}

发送邮箱空闲中断,该中断只要有一个邮箱空闲出来,就会触发中断:

//CAN 发送邮箱发送成功中断回调
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投递消息到消息队列,可以发送下个消息了。。TODO
}
void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投递消息到消息队列,可以发送下个消息了。。TODO
}
void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan)
{// 投递消息到消息队列,可以发送下个消息了。。TODO
}

2.4.4 数据接收 - 中断方式

配置了过滤器只接标准帧后,这个回调中扩展帧就收不到了!

//CAN FIFO0的接收中断处理
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{CAN_RxHeaderTypeDef rxHeader;uint8_t rxData[8]; //接收8字节数据// 读取 FIFO0 中的一条消息if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK){// 打印ID和数据printf("Received CAN message: ID=0x%X DLC=%d\n", rxHeader.StdId, rxHeader.DLC);// 把数据放入你的消息队列。。TODO}else{// 读取失败处理,比如错误计数或重启CAN等}
}

2.5 使用经验总结

  1. CAN通信还需考虑重传吗?

    • CAN 硬件自带重传机制(将开关打开),大多数不需要重传。
    • 广播情况下,发送时不考虑重传,数据压入发送邮箱即可认为发送成功;发送完后,由对端回复丢包情况再进行重传。(此种丢包应该是应用层丢包,比如队列溢出等等。)
  2. 发送邮箱状态检查很重要

    • 发送前应检查发送邮箱是否空闲,避免覆盖未发送完成的报文。
    • 使用 HAL_CAN_GetTxMailboxesFreeLevel() 函数可以方便获取空闲邮箱数量。
  3. 中断中投递消息到任务中处理

    • 发送空闲中断或者接收中断中,不要直接处理数据,投递消息到任务中进行处理。
  4. 其他待补充

3.SocketCan

待完善。

4.CanOpen协议

待完善。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93960.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache ShenYu网关与Nacos的关联及如何配合使用

Apache ShenYu 网关与 Nacos 之间的关系可以概括为 “协作互补”:Nacos 作为 服务注册与配置中心,为 ShenYu 提供动态的服务发现和配置管理能力,而 ShenYu 作为 流量网关,依赖 Nacos 实现路由信息的动态更新和实时生效。以下是详细解析: 1. 核心关系图解 拉取服务列表/路…

【CPP】一个CPP的Library(libXXXcore)和测试程序XXX_main的Demo

一个CPP的Library和测试程序Demo 1. 思路描述 目录结构 总控CMakeList.txt文件 2. Library代码实现 2.1 XXXLib.hpp文件(对外的接口定义文件)和XXXLib.cpp文件 2.1.1 XXXLib.hpp文件 2.1.2 XXXLib.cpp文件 2.2 CXXXLibApi.hpp文件和CXXXLibApi.cpp文件(内部的API基类) 2.2.1 CX…

【YashanDB认证】学习YashanDB的探索之路:从入门到实践

在国产数据库蓬勃发展的浪潮中&#xff0c;选择了YashanDB作为技术学习的切入点。这不仅让我深入了解了数据库的核心技术&#xff0c;也让我深刻体会到国产数据库在性能、可靠性和生态适配上的创新价值。以下是我在学习YashanDB过程中的经验与感悟。 一、YashanDB基础介绍 Ya…

element UI 和 element plus 在组件上有哪些不同

Element UI 和 Element Plus 都是基于 Vue 的桌面端 UI 组件库&#xff0c;由同一团队&#xff08;饿了么前端团队&#xff09;开发和维护。Element Plus 是 Element UI 的升级版&#xff0c;专为 Vue 3 设计&#xff0c;而 Element UI 仅支持 Vue 2。以下是它们在组件层面的主…

【3D重建技术】如何基于遥感图像和DEM等数据进行城市级高精度三维重建?

城市级高精度三维重建是融合多源空间数据&#xff08;遥感图像、DEM、GIS矢量等&#xff09;、计算机视觉与地理信息处理技术的复杂过程&#xff0c;核心目标是构建包含“地形地物&#xff08;建筑、道路、植被等&#xff09;”的真实、高精度三维场景。其流程可分为数据准备、…

【unitrix数间混合计算】3.4 无符号小数部分标记trait(bin_unsigned.rs)

一、源码 这段代码定义了一个类型级二进制小数系统&#xff0c;用于在编译时表示和验证二进制小数部分的有效性。 use crate::number::{F0, BFrac, Bit};/// 标记合法的二进制小数部分类型 pub trait BinFrac: Copy Default static {}// 空小数部分&#xff08;表示值为0&…

从一次 DDoS 的“死亡回放”看现代攻击链的进化

本文记录的是作者上周在测试环境真实踩到的坑。为了让读者能复现并亲手体验防御思路&#xff0c;文末给出了一份最小可运行的 Go 脚本&#xff0c;支持本地压测 日志回放&#xff0c;方便对比加防护前后的差异。攻击现场还原 周一凌晨 2:14&#xff0c;监控群里突然弹出告警&a…

LeetCode热题100--101. 对称二叉树--简单

1. 题目 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a;输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a;输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 2. 题解 /*** Definition for…

Pub/Sub是什么意思

Pub/Sub&#xff08;发布/订阅模式&#xff09;​​ 是一种异步消息通信范式&#xff0c;用于分布式系统中不同组件之间的解耦通信。它的核心思想是将消息的发送方&#xff08;发布者&#xff09;​​ 和接收方&#xff08;订阅者&#xff09;​​ 分离&#xff0c;通过一个中间…

Redisson3.14.1及之后连接阿里云redis代理模式,使用分布式锁:ERR unknown command ‘WAIT‘

文章目录一、问题背景1、问题原因2、阿里云对Redisson的支持二、解决方案1、继续使用Redisson3.14.0版本2、阿里云redis改为直连模式3、升级Redisson版本到 3.47.0一、问题背景 1、问题原因 阿里云Redis分直连和代理模式&#xff0c;其中代理模式是不支持WAIT命令的。 目前尝…

Linux: RAID(磁盘冗余阵列)配置全指南

Linux&#xff1a;RAID&#xff08;磁盘冗余阵列&#xff09;配置一、RAID 核心概念 RAID&#xff08;Redundant Array of Independent Disks&#xff0c;磁盘冗余阵列&#xff09;通过将多个物理磁盘组合为一个逻辑存储设备&#xff0c;实现提升读写性能、增强数据安全性或平衡…

《GPT-OSS 模型全解析:OpenAI 回归开源的 Mixture-of-Experts 之路》

目录 一、引言 二、GPT-OSS 模型简介 1. 版本与定位 2. 架构设计与技术亮点 2.1 Mixture-of-Experts&#xff08;MoE&#xff09;架构 2.2 高效推理机制与优化技术 2.3 模型对比 三、模型部署 1. 安装相关依赖 1.1 uv 安装 1.2 conda 安装 1.3 Transformers 运行 g…

【力扣热题100】双指针—— 接雨水

题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 注意&#xff1a;答案中不可以包含重复的三元组。输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由…

51单片机拼接板(开发板积木)

一、前言 1.1 背景 读书那会儿&#xff08;2013年左右&#xff09;网上接了很多51单片机的设计。 当时有个痛点: 每张板子都是定制的&#xff0c;画板子&#xff0c;打样&#xff0c;写代码需要花费很多时间。 希望有一张板子&#xff0c;能够实现绝大多数单片机的功能&#xf…

使用segment-anything将目标检测label转换为语义分割label

使用segment-anything将目标检测label转换为语义分割label一、segment-anything简介二、segment-anything安装2.1安装方法2.2预训练模型下载三、将目标检测label转换为语义分割label3.1示例代码3.2代码说明一、segment-anything简介 segment-anything是facebookresearch团队开…

【unitrix数间混合计算】3.3 无符号整数标记trait(bin_unsigned.rs)

一、源码 这段代码是用 Rust 语言实现的一个类型级无符号二进制整数系统&#xff0c;通过类型系统在编译时表示和操作二进制数字。这是一种典型的"类型级编程"&#xff08;type-level programming&#xff09;技术。 use crate::number::{U0, Bin, Bit, BinInt};/// …

Python基本语法总结

1.类&#xff08;Class&#xff09;在Python中类&#xff08;Class&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心概念。1.1.类的基本定义最简单的类class Cat:"""这是一个最简单的类"""pass #创建实例 obj Cat()包含方法的类cl…

数据结构05(Java)-- ( 归并排序实质,归并排序扩展问题:小和问题)

前言 本文为本小白&#x1f92f;学习数据结构的笔记&#xff0c;将以算法题为导向&#xff0c;向大家更清晰的介绍数据结构相关知识&#xff08;算法题都出自&#x1f64c;B站马士兵教育——左老师的课程&#xff0c;讲的很好&#xff0c;对于想入门刷题的人很有帮助&#x1f4…

税务专业人员能力构建与发展路径指南

CDA数据分析师证书含金量高&#xff0c;适应了未来数字化经济和AI发展趋势&#xff0c;难度不高&#xff0c;行业认可度高&#xff0c;对于找工作很有帮助。一、税务专业人员的核心能力框架能力维度关键技能要素专业工具与方法论实践输出成果税务法规应用税种政策解读、法规更新…

Linux中rsync使用与inotify实时同步配置指南

Linux中rsync使用与inotify实时同步配置指南 一、rsync 简介 rsync&#xff08;Remote Sync&#xff09;是 Linux 系统下的一款高效数据镜像和备份工具&#xff0c;用于在本地或远程同步文件和目录。 支持本地复制、基于 SSH 的远程同步&#xff0c;以及使用自有 rsync 协议的同…