CAN通信协议

STM32-CAN

1. CAN基础知识

  • CAN简介:

    Controller Area Network,是ISO国际标准化的串行通信协议。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986年德国电气商博世公司开发出面向汽车的CAN通信协议。此后,CAN通过ISO11898及ISO11519进行了标准。现在,CAN的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。

  • CAN协议的特点:

    1. 多主控制: 在总线空闲时,所有单元都可以发送消息,而两个以上的单元同时发送消息时,根据标识符(ID)决定优先级。ID并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜的单元可继续发送消息,仲裁失利的单元则立即停止发送而进行接收工作。
    2. 系统的柔软性: 与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
    3. 通信速度快,距离远: 最高1Mbps(距离小于40m),最远可达10km(速率低于5Kbps)。
    4. 具有错误检测、错误通知和错误恢复功能: 所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其它所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断的重新发送此消息直到成功发送为止(错误恢复功能)。
    5. 故障封闭功能: CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续错误时,可将引起此故障的单元从总线上隔离出去。
    6. 连接节点多: CAN总线是可以同时连接多个单元的总线,可连接的单元总数理论上是没有限制的,但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加。
  • CAN应用场景:

    CAN总线协议已广泛应用在汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。

    车载网络构想:
    在这里插入图片描述

  • CAN标准:

    ISO11898 标准(高速 CAN)和 ISO11519-2 标准(低速 CAN

    总线拓扑图:
    在这里插入图片描述在这里插入图片描述

  • CAN物理层:
    在这里插入图片描述

  • CAN收发器芯片:
    在这里插入图片描述

  • CAN错误状态:

    1. 主动错误状态: 主动错误状态是可以正常参加总线通信的状态。处于主动错误状态的单元检测出错误时,输出主动错误标志。

    2. 被动错误状态: 被动错误状态是易引起错误的状态。处于被动错误状态的单元虽能参加总线通信,但为不妨碍其它单元通信,接收时不能积极地发送错误通知。处于被动错误状态的单元即使检测出错误,而其它处于主动错误状态的单元如果没发现错误,整个总线也被认为是没有错误的。处于被动错误状态的单元检测出错误时,输出被动错误标志。另外,处于被动错误状态的单元在发送结束后不能马上再次开始发送。在开始下次发送前,在间隔帧期间内必须插入“延迟传送”(8个位的隐性位)。

    3. 总线关闭态: 总线关闭态是不能参加总线上通信的状态。信息的接收和发送均被禁止。

      这些状态依靠发送错误计数和接收错误计数来管理,根据计数值决定进入何种状态。
      在这里插入图片描述
      在这里插入图片描述

    • 错误计数值:
      在这里插入图片描述

2. CAN协议

2.1. CAN协议与ISO/OSI基本参照模型的关系

  • CAN协议与ISO/OSI基本参照模型:
    在这里插入图片描述

    数据链路层分为MAC子层和LLC子层,MAC子层是CAN协议的核心部分。数据链路层的功能是将物理层收到的信号组织成有意义的消息,并提供传送错误控制等传输控制的流程。具体地说,就是消息的帧化、仲裁、应答、错误的检测或报告。数据链路层的功能通常在CAN控制器的硬件中执行。

    在物理层定义了信号实际的发送方式、位时序、位的编码方式及同步的步骤。但具体地说,信号电平、通信速度、采样点、驱动器和总线的电气特性、连接器的形态等均未定义。这些必须由用户根据系统需求自行确定。

2.2. CAN协议及标准规格

在这里插入图片描述

2.3. CAN协议

2.3.1. 帧的种类

通信的实现依靠五种帧:数据帧遥控帧错误帧过载帧帧间隔

另外,数据帧和遥控帧有标准格式扩展格式两种格式。标准格式有 11 个位的标识符(ID),扩展格式有 29 个位的 ID。

帧类型帧作用
数据帧(Data Frame)用于发送单元向接收单元传输数据的帧
遥控帧(Remote Frame)用于接收单元向具有相同ID的发送单元请求数据的帧
错误帧(Error Frame)用于当检测出错误时向其他单元通知错误的帧
过载帧(Overload Frame)用于接收单元通知其尚未做好接收准备的帧
间隔帧(Inter Frame Space)用于将数据帧 及遥控帧与前面的帧分离开来的帧
2.3.2. 数据帧
  • 数据帧的构成:
    在这里插入图片描述

    数据帧由7段构成:

    帧起始:表示数据帧开始的段。
    在这里插入图片描述

    仲裁段:表示该帧优先级的段。
    在这里插入图片描述

    控制段:表示数据的字节数及保留位的段。
    在这里插入图片描述

    数据段:数据的内容,可发送 0~8 个字节的数据。
    在这里插入图片描述

    CRC段:检查帧的传输错误的段。
    在这里插入图片描述

    ACK段:表示确认正常接收的段。
    在这里插入图片描述

    帧结束:表示数据帧结束的段。
    在这里插入图片描述

2.3.3. 遥控帧
  • 遥控帧的构成:
    在这里插入图片描述

    遥控帧由6段组成:

    帧起始: 表示帧开始的段。

    仲裁段: 表示该帧优先级的段。

    控制段: 表示数据的字节数及保留位的段。

    CRC段: 表示帧传输错误的段。

    ACK段: 表示确认正常接收的段。

    帧结束: 表示遥控帧结束的段。

  • 数据帧与遥控帧的不同:

    • 遥控帧的RTR位为隐性位,没有数据段
    • 没有数据段的数据帧和遥控帧可通过RTR位区别
  • 遥控帧的数据长度码以所请求数据帧的数据长度码表示

  • 没有数据帧的数据段可用于各单元的定期连接确认/应答、或仲裁段本身带有实质性信息的情况

2.3.4. 错误帧

用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志错误界定符构成。
在这里插入图片描述

  • 错误标志
    • 主动错误标志(处于主动错误状态的单元检测出错误时输出的错误标志):6个位的显性位
    • 被动错误标志(处于被动错误状态的单元检测出错误时输出的错误标志):6个位的隐性位
  • 错误界定符
    • 错误界定符由8个位的隐性位构成。
2.3.5. 过载帧

用于接收单元通知其尚未完成接收准备的帧。过载帧由过载标志过载界定符构成。
在这里插入图片描述

  • 过载标志
    • 6个位的显性位。
    • 过载标志的构成与主动错误标志的构成相同。
  • 过载界定符:
    • 8个位的隐性位。
    • 过载界定符由8个位的隐性位构成。
2.3.6. 间隔帧

帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过帧间隔将本帧与前面的任何帧分开。

过载帧和错误帧不能插入帧间隔。
在这里插入图片描述

  • 间隔:
    • 3个位的隐性位。
  • 延迟传送:
    • 8个位的隐性位。
    • 只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。
  • 总线空闲:
    • 隐性电平,无长度限制。
    • 本状态下,可视为总线空闲,要发送的单元可开始访问总线。

2.4. 优先级的决定

在总线空闲态,最先开始发送消息的单元获得发送权。

多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。
在这里插入图片描述

  • 数据帧和遥控帧的优先级:

    具有相同ID的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权,可继续发送。
    在这里插入图片描述

  • 标准格式和拓展格式的优先级:

    标准格式ID与具有相同ID的遥控帧或者拓展格式的数据帧在总线上竞争时,标准格式的RTR位为显性的具有优先权,可继续发送。
    在这里插入图片描述

2.5. 位填充

位填充是为防止突发错误而设定的功能。当同样的电平持续5位时则添加一个位的反型数据。
在这里插入图片描述

  • 发送单元的工作:

    在发送数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续5位,在下一位(第6个位)则要插入1位与前5位反型的电平。

  • 接收单元的工作:

    在接收数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续5位,需要删除下一位(第6个位)再接收。如果这个第6个位的电平与前5位相同,将被视为错误并发送错误帧。

2.6. 错误的种类

一共有5种错误:位错误填充错误CRC错误格式错误ACK错误
在这里插入图片描述

  • 错误帧的输出位置:
    在这里插入图片描述

2.7. 位时序

由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4段。

  • 同步段(SS)
  • 传播时间段(PTS)
  • 相位缓冲段1(PBS1)
  • 相位缓冲段2(PBS2)

这些段又由可称为Time Quantum (Tq) 的最小时间单位构成。

1位分为4个段,每个段又由若干个Tq构成,这称为位时序。
在这里插入图片描述

1个位的构成:
在这里插入图片描述

  • CAN总线上取的同步的方法:

    CAN协议的通信方法为NRZ(Non-Return Zero)方法。各个位的开头或者结尾都没有附件同步信号。发送单元以与位时序同步的方式开始发送数据。接收单元根据总线上电平的变化进行同步接收工作。但是,发送单元和接收单元存在的时钟频率误差及传输路径上的相位延迟会引起同步偏差。因此,接收单元通过硬件同步再同步的方式调整时序进行接收。

    • 硬件同步:
      在这里插入图片描述

    • 再同步:
      在这里插入图片描述

    • 调整同步的规则:

      1. 1个位中只进行一次同步调整。
      2. 只有当上次采样点的总线值和边沿后的总线值不同时,该边沿才能用于调整同步。
      3. 在总线空闲且存在隐性电平到显性电平的边沿时,则一定要进行硬件同步。
      4. 在总线非空闲时检测到的隐性电平到显性电平的边沿如果满足条件(1)和(2),将进行再同步。但还要满足下面条件。
      5. 发送单元观测到自身输出的显性电平有延迟时不进行再同步。
      6. 发送单元在帧起始到仲裁段有多个单元同时发送的情况下,对延迟边沿不进行再同步。

3. CAN控制器

  • bxCAN介绍:
    在这里插入图片描述

  • CAN控制器工作模式:

    CAN控制器的工作模式有三种:初始化模式正常模式睡眠模式
    在这里插入图片描述

  • CAN控制器测试模式:

    CAN控制器的测试模式有三种:静默模式环回模式环回静默模式
    在这里插入图片描述

  • CAN控制器框图:
    在这里插入图片描述

    发送处理流程:
    在这里插入图片描述
    在这里插入图片描述

    接收处理流程:
    在这里插入图片描述

在这里插入图片描述

接收过滤器:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • CAN控制器位时序:
    在这里插入图片描述

4. CAN相关寄存器

寄存器名称作用
CAN_MCRCAN主控制寄存器主要负责CAN工作模式的配置
CAN_BTR位时序寄存器用来设置分频/TBS1/TBS2/TSWJ等参数,设置测试模式
CAN_(T/R)IxR标识符寄存器存放(待发送/接收)的报文ID、扩展ID、IDE位及RTR位
CAN_(T/R)DTxR数据长度和时间戳寄存器存放(待发送/接收)报文的DLC段
CAN_(T/R)DLxR低位数据寄存器存放 (待发送/接收)报文数据段的Data0~Data3的内容
CAN_(T/R)DHxR高位数据寄存器存放 (待发送/接收)报文数据段的Data4~Data7的内容
CAN_FM1R过滤器模式寄存器用于设置各过滤器组的工作模式
CAN_FS1R过滤器位宽寄存器用于设置各过滤器组的位宽
CAN_FFA1RFIFO关联寄存器用于设置报文通过过滤器后,被存入的FIFO
CAN_FA1R过滤器激活寄存器用于开启对应的过滤器组
CAN_FxR(1/2)过滤器组x寄存器根据位宽和模式设置不同,CAN_FxR1和FxR2功能不同

5. CAN相关HAL库驱动

  • CAN主控制寄存器 (CAN_MCR)
    在这里插入图片描述

  • CAN位时序寄存器 (CAN_BTR)
    在这里插入图片描述

  • CAN发送/接收邮箱标识符寄存器 (CAN_(T/R)IxR)
    在这里插入图片描述

  • CAN发送邮箱数据长度和时间截寄存器 (CAN_TDTxR)
    在这里插入图片描述

  • CAN发送邮箱低字节数据寄存器 (CAN_TDLxR)
    在这里插入图片描述

  • CAN过滤器模式寄存器 (CAN_FM1R)
    在这里插入图片描述

  • CAN过滤器位宽寄存器 (CAN_FS1R)
    在这里插入图片描述

  • CAN过滤器FIFO关联寄存器 (CAN_FFA1R)
    在这里插入图片描述

  • CAN过滤器激活寄存器 (CAN_FA1R)
    在这里插入图片描述

  • CAN过滤器组x寄存器 (CAN_FxR(1/2))
    在这里插入图片描述

6. 代码实现

  • 功能: 通过 KEY_UP 按键选择 CAN 的工作模式(正常模式/环回模式),然后通过 KEY0 控制数据发送,接着查询是否有数据接收到,假如接收到数据,就将接收到的数据显示在 LCD 模块上。如果是环回模式,我们不需要 2 个开发板。如果是正常模式,我们就需要 2 个战舰开发板,并且将他们的 CAN 接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据显示在 LCD 模块上。

  • CAN初始化函数

    void can_init(void)
    {
        CAN_FilterTypeDef can_filterconfig = {0};  // 初始化CAN过滤器配置结构体,将所有成员初始化为0
        
        g_can1_handler.Instance = CAN1;  // 选择CAN1作为CAN控制器实例
        g_can1_handler.Init.Mode = CAN_MODE_LOOPBACK;  // 设置CAN工作模式为环回模式(自发自收),用于调试
        
        g_can1_handler.Init.Prescaler = 4;  // 设置CAN分频系数为4,以降低输入时钟频率
        g_can1_handler.Init.TimeSeg1 = CAN_BS1_9TQ;  // 设置时间段1为9个时间量(TQ)
        g_can1_handler.Init.TimeSeg2 = CAN_BS2_8TQ;  // 设置时间段2为8个时间量(TQ)
        g_can1_handler.Init.SyncJumpWidth = CAN_SJW_1TQ;  // 设置重新同步跳跃宽度为1个时间量(TQ)
         
        g_can1_handler.Init.AutoBusOff = DISABLE;  // 禁止自动离线管理
        g_can1_handler.Init.AutoRetransmission = DISABLE;  // 禁止自动重发消息
        g_can1_handler.Init.AutoWakeUp = DISABLE;  // 禁止自动唤醒功能
        g_can1_handler.Init.ReceiveFifoLocked = DISABLE;  // 禁止接收FIFO锁定
        g_can1_handler.Init.TimeTriggeredMode = DISABLE;  // 禁止时间触发通信模式
        g_can1_handler.Init.TransmitFifoPriority = DISABLE;  // 禁止发送FIFO优先级
        
        HAL_CAN_Init(&g_can1_handler);  // 初始化CAN控制器
        
        can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 设置CAN过滤器模式为ID掩码模式
        can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT;  // 设置CAN过滤器规模为32位
        
        can_filterconfig.FilterIdHigh = 0;  // 设置过滤器ID高位为0
        can_filterconfig.FilterIdLow = 0;  // 设置过滤器ID低位为0
        can_filterconfig.FilterMaskIdHigh = 0;  // 设置过滤器掩码ID高位为0
        can_filterconfig.FilterMaskIdLow = 0;  // 设置过滤器掩码ID低位为0
    
        can_filterconfig.FilterBank = 0;  // 设置过滤器组为0
        can_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0;  // 设置过滤器FIFO分配为FIFO 0
        can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;  // 启用过滤器
        can_filterconfig.SlaveStartFilterBank = 14;  // 设置从过滤器组起始号为14
        HAL_CAN_ConfigFilter(&g_can1_handler, &can_filterconfig);  // 配置CAN过滤器
          
        HAL_CAN_Start(&g_can1_handler);  // 启动CAN控制器
    }
    

    1. 配置CAN控制器实例

    g_can1_handler.Instance = CAN1;
    g_can1_handler.Init.Mode = CAN_MODE_LOOPBACK; // 工作模式设置:环回模式,自发自收
    
    • g_can1_handler.Instance = CAN1;:选择CAN1作为CAN控制器实例。
    • g_can1_handler.Init.Mode = CAN_MODE_LOOPBACK;:将CAN设置为环回模式,这意味着控制器会自发自收,用于调试。

    2. 配置CAN时间和同步参数

    g_can1_handler.Init.Prescaler = 4;                 // 分频系数
    g_can1_handler.Init.TimeSeg1 = CAN_BS1_9TQ;        // 时间段1
    g_can1_handler.Init.TimeSeg2 = CAN_BS2_8TQ;        // 时间段2
    g_can1_handler.Init.SyncJumpWidth = CAN_SJW_1TQ;   // 重新同步跳跃宽度
    
    • g_can1_handler.Init.Prescaler = 4;:设置分频系数为4,以降低输入时钟频率。
    • g_can1_handler.Init.TimeSeg1 = CAN_BS1_9TQ;:设置时间段1为9个时间量(TQ)。
    • g_can1_handler.Init.TimeSeg2 = CAN_BS2_8TQ;:设置时间段2为8个时间量(TQ)。
    • g_can1_handler.Init.SyncJumpWidth = CAN_SJW_1TQ;: 设置同步跳跃宽度为1个时间量(TQ)。

    3. 配置CAN功能

    g_can1_handler.Init.AutoBusOff = DISABLE;          // 禁止自动离线管理
    g_can1_handler.Init.AutoRetransmission = DISABLE;  // 禁止自动重发
    g_can1_handler.Init.AutoWakeUp = DISABLE;          // 禁止自动唤醒
    g_can1_handler.Init.ReceiveFifoLocked = DISABLE;   // 禁止接收FIFO锁定
    g_can1_handler.Init.TimeTriggeredMode = DISABLE;   // 禁止时间触发通信模式
    g_can1_handler.Init.TransmitFifoPriority = DISABLE;// 禁止发送FIFO优先级
    
    • g_can1_handler.Init.AutoBusOff = DISABLE;:禁止自动离线管理。
    • g_can1_handler.Init.AutoRetransmission = DISABLE;:禁止自动重发消息。
    • g_can1_handler.Init.AutoWakeUp = DISABLE;:禁止自动唤醒功能。
    • g_can1_handler.Init.ReceiveFifoLocked = DISABLE;:禁止接收FIFO锁定。
    • g_can1_handler.Init.TimeTriggeredMode = DISABLE;:禁止时间触发通信模式。
    • g_can1_handler.Init.TransmitFifoPriority = DISABLE;:禁止发送FIFO优先级。

    4. 初始化CAN控制器

    HAL_CAN_Init(&g_can1_handler); //调用HAL_CAN_Init函数来初始化CAN控制器
    

    5. 配置CAN过滤器

    can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK; //设置CAN过滤器模式为ID掩码模式
    can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT; //设置CAN过滤器规模为32位
        
    can_filterconfig.FilterIdHigh = 0;  // 设置过滤器ID高位为0
    can_filterconfig.FilterIdLow = 0;  // 设置过滤器ID低位为0
    can_filterconfig.FilterMaskIdHigh = 0;  // 设置过滤器掩码ID高位为0
    can_filterconfig.FilterMaskIdLow = 0;  // 设置过滤器掩码ID低位为0
    
    can_filterconfig.FilterBank = 0;  // 设置过滤器组为0
    can_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0;//设置过滤器FIFO分配为FIFO 0
    can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;  // 启用过滤器
    can_filterconfig.SlaveStartFilterBank = 14;  // 设置从过滤器组起始号为14
    HAL_CAN_ConfigFilter(&g_can1_handler, &can_filterconfig);  // 配置CAN过滤器
    
    • can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK;:设置过滤器模式为ID掩码模式。
    • can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT;:设置过滤器规模为32位。
    • can_filterconfig.FilterIdHigh = 0;can_filterconfig.FilterIdLow = 0;:设置过滤器ID高位和低位为0。
    • can_filterconfig.FilterMaskIdHigh = 0;can_filterconfig.FilterMaskIdLow = 0;:设置过滤器掩码ID高位和低位为0。
    • can_filterconfig.FilterBank = 0;:设置过滤器组为0。
    • can_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0;:设置过滤器FIFO分配为FIFO 0。
    • can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;:启用过滤器。
    • can_filterconfig.SlaveStartFilterBank = 14;:设置从过滤器组起始号为14。

    然后调用HAL_CAN_ConfigFilter函数来配置过滤器。

    6. 启动CAN控制器

    HAL_CAN_Start(&g_can1_handler); //调用HAL_CAN_Start函数来启动CAN控制器
    

    具体操作寄存器:在这里插入图片描述

  • MSP回调函数

    void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
    {
        CAN_TX_GPIO_CLK_ENABLE();  // 启用CAN发送引脚的GPIO时钟
        CAN_RX_GPIO_CLK_ENABLE();  // 启用CAN接收引脚的GPIO时钟
        __HAL_RCC_CAN1_CLK_ENABLE();  // 启用CAN1控制器的时钟
    
        GPIO_InitTypeDef gpio_init_struct;  // 定义一个GPIO初始化结构体
    
        // 配置CAN发送引脚
        gpio_init_struct.Pin = CAN_TX_GPIO_PIN;  // 设置引脚号
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;  // 设置为复用推挽输出模式
        gpio_init_struct.Pull = GPIO_PULLUP;  // 上拉电阻
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;  // 高速
        HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct);  // 初始化CAN发送引脚的GPIO配置
    
        // 配置CAN接收引脚
        gpio_init_struct.Pin = CAN_RX_GPIO_PIN;  // 设置引脚号
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;  // 设置为复用输入模式
        HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_init_struct);  // 初始化CAN接收引脚的GPIO配置
    }
    

    1. 配置CAN发送引脚(TX):

    gpio_init_struct.Pin = CAN_TX_GPIO_PIN;   // 设置引脚号
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;  // 设置为复用推挽输出模式
    gpio_init_struct.Pull = GPIO_PULLUP;      // 上拉电阻
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;  // 高速
    HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct);  // 初始化CAN发送引脚的GPIO配置
    
    • gpio_init_struct.Pin = CAN_TX_GPIO_PIN:指定要配置的GPIO引脚为CAN发送引脚。
    • gpio_init_struct.Mode = GPIO_MODE_AF_PP:将引脚模式设置为复用推挽输出模式,用于CAN发送。
    • gpio_init_struct.Pull = GPIO_PULLUP:使能上拉电阻,保持引脚在空闲状态时为高电平。
    • gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH:将引脚速度设置为高频率,以支持高速数据传输。
    • HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct):使用以上配置初始化CAN发送引脚的GPIO。

    2. 配置CAN接收引脚(RX):

    gpio_init_struct.Pin = CAN_RX_GPIO_PIN;      // 设置引脚号
    gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;  // 设置为复用输入模式
    HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_init_struct);  // 初始化CAN接收引脚的GPIO配置
    
    • gpio_init_struct.Pin = CAN_RX_GPIO_PIN:指定要配置的GPIO引脚为CAN接收引脚。
    • gpio_init_struct.Mode = GPIO_MODE_AF_INPUT:将引脚模式设置为复用输入模式,用于CAN接收。
  • 发送消息数据函数

    void can_send_message(uint32_t id, uint8_t *buf, uint8_t len)
    {
        uint32_t tx_mail = CAN_TX_MAILBOX0;  // 定义并初始化发送邮箱为邮箱0
        
        g_can1_txheader.ExtId = id;          // 设置扩展ID为传入的id参数
        g_can1_txheader.DLC = len;           // 设置数据长度代码(DLC)为传入的len参数
        g_can1_txheader.IDE = CAN_ID_EXT;    // 设置标识符扩展(IDE)为扩展帧
        g_can1_txheader.RTR = CAN_RTR_DATA;  // 设置远程传输请求(RTR)为数据帧
        
        HAL_CAN_AddTxMessage(&g_can1_handler, &g_can1_txheader, buf, &tx_mail);  // 通过CAN控制器发送消息
        
        while(HAL_CAN_GetTxMailboxesFreeLevel(&g_can1_handler) != 3);  // 等待所有发送邮箱为空
    }
    

    1. 定义并初始化发送邮箱:

    uint32_t tx_mail = CAN_TX_MAILBOX0;  // 定义并初始化发送邮箱为邮箱0
    
    • 定义一个32位无符号整数tx_mail并将其初始化为CAN_TX_MAILBOX0,表示使用第一个发送邮箱。

    2. 设置CAN消息头:

    g_can1_txheader.ExtId = id;          // 设置扩展ID为传入的id参数
    g_can1_txheader.DLC = len;           // 设置数据长度代码(DLC)为传入的len参数
    g_can1_txheader.IDE = CAN_ID_EXT;    // 设置标识符扩展(IDE)为扩展帧
    g_can1_txheader.RTR = CAN_RTR_DATA;  // 设置远程传输请求(RTR)为数据帧
    
    • g_can1_txheader.ExtId = id:将传入的id参数设置为CAN消息的扩展ID。
    • g_can1_txheader.DLC = len:将传入的len参数设置为CAN消息的数据长度代码(DLC)。
    • g_can1_txheader.IDE = CAN_ID_EXT:将CAN消息的标识符扩展(IDE)设置为扩展帧,表示使用29位ID。
    • g_can1_txheader.RTR = CAN_RTR_DATA:将CAN消息的远程传输请求(RTR)设置为数据帧。

    3. 发送CAN消息:

    HAL_CAN_AddTxMessage(&g_can1_handler, &g_can1_txheader, buf, &tx_mail);  // 通过CAN控制器发送消息
    
    • 调用HAL_CAN_AddTxMessage函数,通过CAN控制器发送消息:
      • &g_can1_handler:指向CAN控制器的句柄。
      • &g_can1_txheader:指向消息头的指针。
      • buf:指向要发送的数据缓冲区。
      • &tx_mail:指向发送邮箱的指针。

    4. 等待发送完成:

    while(HAL_CAN_GetTxMailboxesFreeLevel(&g_can1_handler) != 3);  // 等待所有发送邮箱为空
    
    • 使用while循环等待所有发送邮箱为空:
      • HAL_CAN_GetTxMailboxesFreeLevel(&g_can1_handler):获取当前空闲的发送邮箱数量。
      • != 3:如果空闲的发送邮箱数量不等于3,则继续等待。这表示所有发送邮箱(CAN_TX_MAILBOX0, CAN_TX_MAILBOX1, CAN_TX_MAILBOX2)都空闲。

    总结:

    这段代码主要通过配置CAN消息的头部(包括ID、长度、帧类型等)并调用相应的库函数发送消息,最后等待所有发送邮箱都空闲,以确保消息成功发送。

  • 接收数据函数

    uint8_t can_receive_message(uint8_t *buf)
    {
        if(HAL_CAN_GetRxFifoFillLevel(&g_can1_handler, CAN_RX_FIFO0) == 0)
        {
           return 0;
        }
        
        HAL_CAN_GetRxMessage(&g_can1_handler, CAN_RX_FIFO0, &g_can1_rxheader, buf);
        
        return g_can1_rxheader.DLC;
    }
    

    1. 检查接收FIFO队列是否有消息:

    if(HAL_CAN_GetRxFifoFillLevel(&g_can1_handler, CAN_RX_FIFO0) == 0)  // 检查接收FIFO0队列是否为空
    {
       return 0;  // 如果接收FIFO0队列为空,返回0,表示没有消息
    }
    
    • HAL_CAN_GetRxFifoFillLevel(&g_can1_handler, CAN_RX_FIFO0):调用库函数检查接收FIFO0队列的填充级别。接收FIFO0队列为空,即没有消息。
      • &g_can1_handler:指向CAN控制器的句柄。
      • CAN_RX_FIFO0:指定接收FIFO0队列。

    2. 从接收FIFO0队列中获取消息:

    HAL_CAN_GetRxMessage(&g_can1_handler, CAN_RX_FIFO0, &g_can1_rxheader, buf);  // 从接收FIFO0队列中获取消息,并存储在buf中
    
    • HAL_CAN_GetRxMessage(&g_can1_handler, CAN_RX_FIFO0, &g_can1_rxheader, buf):调用库函数从接收FIFO0队列中获取消息。
      • &g_can1_handler:指向CAN控制器的句柄。
      • CAN_RX_FIFO0:指定接收FIFO0队列。
      • &g_can1_rxheader:指向接收消息头部的指针。
      • buf:指向接收消息数据缓冲区的指针。

    3. 返回消息的长度代码(DLC):

    return g_can1_rxheader.DLC;  // 返回消息的长度代码(DLC)
    
    • g_can1_rxheader.DLC:获取接收消息头部的长度代码(DLC)。

    总结:

    这段代码用于接收通过CAN(控制器局域网络)发送的消息,并将其存储在指定的缓冲区中。它首先检查接收FIFO(先入先出)队列中是否有消息,如果有消息,函数从FIFO队列中提取消息,并返回消息的长度代码(DLC)。

声明:资料来源(战舰STM32F103ZET6开发板资源包)

  1. Cortex-M3权威指南(中文).pdf
  2. STM32F10xxx参考手册_V10(中文版).pdf
  3. STM32F103 战舰开发指南V1.3.pdf
  4. STM32F103ZET6(中文版).pdf
  5. 战舰V4 硬件参考手册_V1.0.pdf
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值