CAN总线/CAN应用层协议设计,理解并实践仲裁段位域定义

文章目录

  • 概述
  • 位序和字节序
  • 驱动层CAN帧数据结构
    • 待发送数据的结构
    • 待接收数据的结构
    • 仲裁段使用U32的低位?
  • 位段结构和寄存器位的映射
  • 数据段的发送顺序
  • CAN协议定义
    • 协议文档中的定义
    • 仲裁段定义
    • 数据段定义
    • 跨平台CAN通信

概述

我们已然明确地知道,CAN仲裁段的定义,决定了CAN帧在物理总线上的传送优先级。那么,该怎么定义这个仲裁段,或者说如何定义CAN标准帧或扩展帧的CanID位段结构,才能按照自己的需求控制各设备发出的CAN帧的优先级呢?CAN帧的仲裁段和数据段的信息组织,是否要考虑大小端字节序?

@History
参考《语言基础/分析和实践 C&C++ 位域结构数据类型》文中的讲述,其中也简单的提到了CAN协议仲裁段定义的注意事项。在接下来的嵌入式开发项目中,又要用到CAN通信,4年前的使用只是浮于表面,这回算是正是入手了。在整理上述关于位域的CSDN博文时,将与CAN仲裁段位域定义的相关部分单独提溜了出来,结合对 STM32 CAN 驱动源码的阅读,写了此文。

位序和字节序

以太网协议规定了数据的字节传输顺序,即网络字节序,但并没有规定数据的位传输顺序。与之不同的是,CAN协议本身并没有规定字节发送顺序,它只规定了数据的位传输顺序。通常说,位传输顺序是由具体的硬件实现决定的,可以是从左到右(Most Significant Bit First,MSb)或从右到左(Least Significant Bit First,LSb)的顺序,这里的硬件可能包括但不限于,如,数据寄存器、移位寄存器等(仅是个人理解哈)。在CAN协议中,数据的位传输顺序是由协议本身明确定义的,所有厂商制造的CAN外设都要按照此规范来实现。
在这里插入图片描述
在计算中,最低有效位(LSb)是二进制整数中表示整数二进制1的位位置,最高有效位(MSb)表示二进制整数的最高位。由于在位值记法(我们平时对二进制数的阅读法)中将较不显著的数字写在右边,因此LSb有时被称为低阶位或最右边位。类似地,MSb被称为高阶位或最左边位。在维基百科 Bit numbering 的描述如上,两种不同的位传输顺序。“Most significant bit first”和“least significant bit at last” 这两个表述指示了在串行传输协议或流(例如音频流)中发送的字节中比特序列的排序方式。“Most significant bit first”意味着最重要的比特将首先到达:因此,例如十六进制数0x12,在二进制表示中为00010010,将以顺序0 0 0 1 0 0 1 0到达。“Least significant bit first” 意味着最不重要的比特将首先到达:因此,例如相同的十六进制数0x12,再次是00010010的二进制表示,将以(反转的)顺序0 1 0 0 1 0 0 0到达。

字节序是小端的 CPU 通常其位序为 LSb 0,不仅是数据字节在内存中“高高低低”存放,字节的位序也是“高高低低”放置的,即 MSb 存放在 bit7 位置上,LSb 放置在 bit0 位置上。值得注意的是,字节序是大端的 CPU 采用的位序却不是那么统一,既有 MSb 0,也有LSb0 。一个常规的样子如下,
在这里插入图片描述
结合近期关于大小端、结构体、位段结构等主题文章的整理和学习,我基本可以推论,CAN通信在协议层次上强势规定传输位序,是比以太网规定字节序更加严苛的规定,这种规定,使得CAN通信不必关注通信双方的字节序模式,各通信设备根据自身情况去解读bit位就可以。但这只是推论,对目前的我来说,去构建一个32bit的大端环境(参见《存储和传输/寻找大端字节序》),都要耗费心神还难以搞定。让我在搭建一个不光支持大端字节序还要支持CAN通信的环境,就更难为我了。

首先的一个前提是,无论标准帧的仲裁段11bit还是扩展帧的29bit,都不会超过寄存器位宽32bit,即不超过机器字长位宽,这么说可能有点欠妥,明白那个意思就行了。如果我是芯片制造厂商的设计者,我会充分利用高位到低位的传输约定,在结合自身CPU大小端字节序的特点,直接将收到的位数据转换为符合自身字节序的字节存储。再不济,从CAN寄存器中取出数据时,在驱动层通过软手段,也能灵活的将位数据映射为大端存储或小端存储的内存数据。

驱动层CAN帧数据结构

我们已经知道CAN协议是定义了发送位序的,从高位到低位顺序发送。

待发送数据的结构

在这里插入图片描述
在 STM32F4xx_StdPeriph_Driver\inc\stm32f4xx_can.h 头文件定义,

typedef struct {
      //river.qu @CSDN
  uint32_t StdId;  /*! 存储标准帧的标识符,11bit 0 to 0x7FF*/
  uint32_t ExtId;  /*! 存储标准帧的标识符,29bit 0 to 0x1FFFFFFF. */
  uint8_t IDE;     /*! 取值 CAN_ID_STD(表示标准帧)或 CAN_ID_EXT(表示扩展帧) */
  uint8_t RTR;     /*! 取值 CAN_RTR_DATA(表示数据帧)或 CAN_RTR_REMOTE(表示远程帧) */
  uint8_t DLC;     /*! 表示数据帧中的实际数据长度,范围为0-8  */
  uint8_t Data[8]; /*! 用于存储CAN消息的数据部分,最多包含8个字节的数据  */
} CanTxMsg;

首先映入眼帘的是 StdId 和 ExtId 是并列定义的,而不是union联合定义,说实话,我不是太理解这是出于怎样的考虑?
理论上来说,标准帧和扩展帧不能同时存在,同一时刻只有StdId或ExtId字段中的一个会被填充,无疑,此时使用联合结构可以更加节省空间。有点牵强的说法是,上述并列定义可以帮助开发人员在同一个结构体中方便地存储和处理不同类型的CAN标识符,提供了更大的灵活性和可扩展性,有更好的可读性和可维护性。另外,这种并列方式的定义,可能使得该驱动代码,可以更好地兼容和适应不同的CAN硬件和软件实现,以确保代码在不同的平台和芯片上的可移植性。这里不再深究,认定 STM32 芯片 CAN 驱动的开发者们是基于合理的、我不太理解的原因,如此定义,哈哈,毕竟我们只是普通的使用者,也改变不了它。

待接收数据的结构

在这里插入图片描述

仲裁段使用U32的低位?

结合上文,标识仲裁段位数据的 StdId 和 ExtId 都是 uint32_t 数据类型,但是它们都不足32bit呢,于是就产生了题目中的问题。以 StdId 为例,CAN 标准帧的11 位仲裁位是 U32的高11位 [0-10]bit,还是U32的低11位 [21-31]bit 呢?

为此,我们找到CAN的发送函数实现,如下,

/**
  * @brief  Initiates and transmits a CAN frame message.
  * @param  CANx: where x can be 1 or 2 to to select the CAN peripheral.
  * @param  TxMessage: pointer to a structure which contains CAN Id, CAN DLC and CAN data.
  * @retval The number of the mailbox that is used for transmission or
  *         CAN_TxStatus_NoMailBox if there is no empty mailbox.
  */
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
{
   
  uint8_t transmit_mailbox = 0;
  /* Check the parameters */
  assert_param(IS_CAN_ALL_PERIPH(CANx));
  assert_param(IS_CAN_IDTYPE(TxMessage->IDE));
  assert_param(IS_CAN_RTR(TxMessage->RTR));
  assert_param(IS_CAN_DLC(TxMessage->DLC));

  /* Select one empty transmit mailbox */
  if ((CANx->TSR&CAN_TSR_TME0) == CAN_TSR_TME0)  {
   
    transmit_mailbox = 0;
  }
  else if ((CANx->TSR&CAN_TSR_TME1) =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值