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

news2024/9/24 21:24:38

文章目录

  • 概述
  • 位序和字节序
  • 驱动层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) == CAN_TSR_TME1)  {
    transmit_mailbox = 1;
  }
  else if ((CANx->TSR&CAN_TSR_TME2) == CAN_TSR_TME2) {
    transmit_mailbox = 2;
  }
  else {
    transmit_mailbox = CAN_TxStatus_NoMailBox;
  }

  if (transmit_mailbox != CAN_TxStatus_NoMailBox)  {
    /* Set up the Id */
    CANx->sTxMailBox[transmit_mailbox].TIR &= TMIDxR_TXRQ;
    if (TxMessage->IDE == CAN_Id_Standard)  {
      assert_param(IS_CAN_STDID(TxMessage->StdId));  
      CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->StdId << 21) | \
                                                  TxMessage->RTR);
    }
    else {
      assert_param(IS_CAN_EXTID(TxMessage->ExtId));
      CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->ExtId << 3) | \
                                                  TxMessage->IDE | \
                                                  TxMessage->RTR);
    }
    
    /* Set up the DLC */
    TxMessage->DLC &= (uint8_t)0x0000000F;
    CANx->sTxMailBox[transmit_mailbox].TDTR &= (uint32_t)0xFFFFFFF0;
    CANx->sTxMailBox[transmit_mailbox].TDTR |= TxMessage->DLC;

    /* Set up the data field */
    CANx->sTxMailBox[transmit_mailbox].TDLR = (((uint32_t)TxMessage->Data[3] << 24) | 
                                             ((uint32_t)TxMessage->Data[2] << 16) |
                                             ((uint32_t)TxMessage->Data[1] << 8) | 
                                             ((uint32_t)TxMessage->Data[0]));
    CANx->sTxMailBox[transmit_mailbox].TDHR = (((uint32_t)TxMessage->Data[7] << 24) | 
                                             ((uint32_t)TxMessage->Data[6] << 16) |
                                             ((uint32_t)TxMessage->Data[5] << 8) |
                                             ((uint32_t)TxMessage->Data[4]));
    /* Request transmission */
    CANx->sTxMailBox[transmit_mailbox].TIR |= TMIDxR_TXRQ;
  }
  return transmit_mailbox;
}

其中关于仲裁段设置的代码如下,
在这里插入图片描述
上述代码中,其中 TIR 是发送邮箱结构定义中的字段,其结构体定义如下,

/** 
  * @brief Controller Area Network TxMailBox 
*/
typedef struct {
  __IO uint32_t TIR;  /*!< CAN TX mailbox identifier register */
  __IO uint32_t TDTR; /*!< CAN mailbox data length control and time stamp register */
  __IO uint32_t TDLR; /*!< CAN mailbox data low register */
  __IO uint32_t TDHR; /*!< CAN mailbox data high register */
} CAN_TxMailBox_TypeDef;

邮箱 TIR 是 TX mailbox identifier register 的意思,即发送邮箱标识符寄存器,也就是CAN仲裁段在发送出去的前一刻所在的位置。结合上文对TIR的设置代码,在标准帧模式下,32位的StdId,先是左移了21位,也就是说高21位被干掉了,即CAN底层使用的是StdId的低11位。同样地,32位的ExtdId在传递给邮箱 TIR 字段时,抛弃了高3位,使用的是其ExtdId的低29位。
分析至此变可以明确:在CAN应用层传入的32位的CanID值,如果是标准帧ID,则高21位被抛弃;如果是扩展帧ID,则高3bit最终被驱动层抛弃。在CAN驱动层会截取其低11bit(标准帧) 或 29bit(扩展帧) 来最终使用,因此,具体标准帧ID和扩展帧ID的的位段结构定义中,保留位是在末尾的字段哦,具体参见文末章节中的定义。

位段结构和寄存器位的映射

在《语言基础/分析和实践 C&C++ 位域结构数据类型》文中的讲述,已经足够证明,最后出现的字段占据数值的高位区,最后出现字段的最高位,对应的是存储字段内存区的MSb。直觉告诉我,CAN的高位发送,其中的高位,应该是指存储发送数据的寄存器的高位。

在进一步讨论前,要先明确的2点是,
1、对于寄存器而言,通常不涉及字节序的概念,因为寄存器中的位和比特是按照位的顺序来定义和访问的,而不是按照字节来分割。在寄存器级别的操作中,通常会直接操作寄存器的位(bit),而不是以字节为单位进行操作。
2、邮箱是软件和硬件之间传递报文的接口,其包含了所有跟报文有关的标识符、数据、控制、状态和时间戳信息。
在这里插入图片描述
与 F407 一样,103也通常是3个发送邮箱,如下图,每个邮箱包含4个寄存器。CAN 发送邮箱标识符寄存器 (CAN_TIxR) (x=0…2) / CAN 邮箱数据长度控制和时间戳寄存器 (CAN_TDTxR) (x=0…2) / CAN 邮箱数据低位寄存器 (CAN_TDLxR) (x=0…2) / CAN 邮箱数据高位寄存器 (CAN_TDHxR) (x=0…2)

我进一步想去验证的是,我从应用层传递到驱动层的CanID,等存储到CAN的发送邮箱中的 TIR 标识符寄存器中后,其位序或值是否完全一致,有没有位的反转或什么别的啥?或者我当时就想简单的看看 CAN_TxMailBox_TypeDef 中的TIR 内存字段值与直接读外设寄存器值,是不是完全一样?小端上的内存位序和寄存的位序难道会有不同?

我们重点关注 TIR 发送邮箱标识符寄存器,其位分配如下,
在这里插入图片描述
使用如下代码,标准帧进行如下测试过程,

#define MR_U32 unsigned int
//StdID 标准帧
typedef struct {
    MR_U32 bitDevID:5;     //设别标识
    MR_U32 bitDevType:4;   //设别类型
    MR_U32 bitPriority:2;  //优先级
    MR_U32 bitReserve:21;  //高位不使用 /参见CAN驱动代码
} TCanIDStd;
//
typedef union {
	TCanIDStd tCanStdID;
	MR_U32    u32CanStdID;
} UStdID;

//测试用用发送函数
u8 CAN1_Send_Msg(u8* msg,u8 len) {	
    u8 i=0;
  	u32 TxMailbox;
	u8 message[8];
	UStdID uStdID;
	//仲裁段赋值	
	uStdID.tCanStdID.bitPriority = 1;
	uStdID.tCanStdID.bitDevType = 2;
	uStdID.tCanStdID.bitDevID = 3;
	uStdID.tCanStdID.bitReserve = 0;

	TxHeader.StdId = uStdID.u32CanStdID;
	TxHeader.ExtId = 0;
    TxHeader.IDE=CAN_ID_STD;    //使用标准帧
    TxHeader.RTR=CAN_RTR_DATA;  //数据帧
    TxHeader.DLC=len;   
	
    for(i=0;i<len;i++) {
			message[i]=msg[i];
	}
    if(HAL_CAN_AddTxMessage(&CAN1_Handler, &TxHeader, message, &TxMailbox) != HAL_OK) {
			return 1;
	}
	...
}

按照如上代码赋值的 StdId 其值和内存字节如下图,
在这里插入图片描述
如上,4字节的 StdId ,在小端的STM32中,其存储为 43 02 00 00,翻译为二进制为,
在这里插入图片描述
如上,uStdID.u32CanStdID 其中的第一个字段 bitDevID 被放置在了最低bit位。
在这里插入图片描述
先要明确的是,CAN的从高位发送,更具体的说应该是,从发送邮箱的第一个寄存器TIR的最高位开始发送。接下来是要重点验证的地方,TxHeader.StdId 的低11bit,被安置到TIR寄存器中。STDID[10] 被安置在 TIR [31],按照上述TIR寄存器位配置,
在这里插入图片描述
翻译成16进制数据,0x48600000,我们来监视TIR寄存器的值,是否符合预期。在 View 菜单的 System Viewer 子菜单中打开 CAN 寄存器监视窗口(以普通103为例,这例要监视的是 CAN_TI0R寄存器),如下图,
在这里插入图片描述
结果符合预期。what?寄存器中反读到的值,与TIR值一致,其中的CanID部分也与应用层传入的值一致,位序也匹配。

数据段的发送顺序

我们实际压入CAN数据区的数据可能是U16、U32、U64等多字节整数值或位域结构值。假如我们的实际数据是U64的位域值,如文末给出的 TCanDenoDt位段结构数据,接下来,我们将看看数据段的位发送顺序或者是字节发送顺序。

参照前文给出的待发送CAN帧的驱动层数据结构,数据段形式为 U8 Data[8]。结合《存储与传输/大小端字节序的概念、决定因素、给编程带来的困扰》中的讲述,数组在内存中的存储本质与结构对象无异,下标为0的元素,在内存存储位置上与结构体第一字段存储位置一致,无论是大端还是小端系统,Data[0]一定是在内存低地址上的,加之这里的数据段元素是单字节,因此,从交互的角度上看,似乎没有大小端问题呢?要考虑的,只是U64和U8 Data[8]之间(可以将他们放在一个假想的联合结构内)的字节映射关系。Data[0]指向的是内存最低地址,Data[7]指向的是内存最高地址。至此,问题就可以具体到CAN上是先发送Data[7](小端:高地址,高字节)还是Data[0](低地址,低字节)呢?

uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage) {
  	...
    /* Set up the data field */
    CANx->sTxMailBox[transmit_mailbox].TDLR = (((uint32_t)TxMessage->Data[3] << 24) | 
                                             ((uint32_t)TxMessage->Data[2] << 16) |
                                             ((uint32_t)TxMessage->Data[1] << 8) | 
                                             ((uint32_t)TxMessage->Data[0]));
    CANx->sTxMailBox[transmit_mailbox].TDHR = (((uint32_t)TxMessage->Data[7] << 24) | 
                                             ((uint32_t)TxMessage->Data[6] << 16) |
                                             ((uint32_t)TxMessage->Data[5] << 8) |
                                             ((uint32_t)TxMessage->Data[4]));
	...
  return transmit_mailbox;
}

如上CAN_Transmit函数中显示的那样,Data[7]被放在了TDHR数据高寄存器的最高字节中,Data[0]被放在了TDLR数据低寄存器的最低字节中。下图是F407手册中,CAN 邮箱数据高位寄存器 (CAN_TDHxR) 的32bit位,与Data字节之间的关系图,
在这里插入图片描述
如上图,DATA7[7]对应的是CAN邮箱高位寄存器的最高bit位,再结合CAN协议从高位到低位发送顺序的约定,这里可以大胆的猜测,CAN上最先发送出去的就是高位寄存器中存储的最高位,即上述DATA7[7],其后发送的是DATA7[6]…DATA6[7]…DATA6[0]…DATA4[0]…DATA3[7]…DATA3[0]…DATA0[0],共计64bit数据。

也即可以确定,数据字节发送顺序是Data[7]到Data[0],这倒是与网络字节序发送顺序相反,在以太网数据传输中,数据字节流从低地址到高地址向外发送,这里反而是从高地址到低地址进行发送(要强调的一点是,网络字节序规定了多字节数据要转成大端后再进行传输,但TCP/IP本身并不去关心传输的数据是什么字节序的,其按照内存字节流顺序向远端发送)。Data[7]对应的是U64的高字节,因此,在stm32这个小端MCU中,CAN数据段先传递到远端的是64bit数据的最高有效字节,且最高有效字节的最高有效位(下图bit63)最先先传输,
在这里插入图片描述
如上图,在小端系统上,以U64的数据u64Data=0x1122334455667788为例,注明了该数据在小端内存中的存储,其与Data[8]各元素对应关系,位序和位取值。
再次强调,所谓网络字节序的约定,是期望你把所有带发送数据先转换成大端字节序,回存到数据发送缓冲区的内存中,然后发送这个转换后的字节流。TCP/IP不关心你要发送的字节流是什么,当然不会替你转换,如果你自己不去转换,而是直接发送,那么内存中存储的全部字节流的从低地址到搞地地址的存储顺序就是最终的数据字节传输顺序。网络传输上不关注位顺序,如上,位顺序大概率是7-0,15-8, …, 63-56,具体的不再深究,可能要用示波器等硬件信号分析仪来深入探究。总之,对于没有经过人为转换的u64Data原始存储数据,在网络上,先传输Data[0],最后传输Data[7]。

而CAN数据段上,在传输上述u64Data原始存储数据时,其字节顺序很明显是相反的,先传输Data[7],最后传输Data[0]。这有点像是,有的像是,CAN驱动层默默的替你做将小端字节序转位大端字节序这个事情。
假设上述通信过程的CAN远端是一个大端系统(发送端是小端系统),
我想,只是猜测哈,在CAN硬件和驱动层的加持下,可以保证接收端 TDHR 和 TDLR 两个寄存器的值与发送端相应寄存器的值是一致的。如此一来,接收端的CAN驱动层,完全可以在判断出自己是大端或小端后,将TDHR 和 TDLR 两个寄存器的位数据映射成不同的内存字节顺序。这样就在CAN驱动层上屏蔽了大小端字节序,在CAN应用层就不必要在去执行和关系大小端字节序转换问题。

上述大多只是猜想哈,我现在没能力和时间搭建一个大小端系统间的CAN通信环境。好在,一般情况下,小端系统,尤其是在嵌入式开发中,其主流趋势已经达到了几乎垄断的地步,我们不太需要过度关注CAN通信上的大小端字节序问题。我们只要重点关注CAN仲裁段的位序和位段结构定义就好。

CAN协议定义

本章节将描述CAN协议文档的编写方式、仲裁段位段结构定义、数据段位段结构定义。

协议文档中的定义

在协议文档中,我们通常会把仲裁段中的要优先发送的字段写在文档的最前边,毕竟文档要符合人的阅读习惯。以扩展帧定义为例,
在这里插入图片描述

结合《语言基础/分析和实践 C&C++ 位域结构数据类型》和《存储和传输/探究结构数据(C/C++结构体)在内存中的对齐和填充规则》文中的相关分析,我们已经清晰的知道(位段和字段的概念是不一样的哈,多个位段合起来占用一个存储字段,可参考链接中的文章),位段结构定义中先出现的位段占用的是结构存储字段的低bit位,后出现的位段占据的结构存储字段的高bit位。故,会有如下现象:文档中的位段定义顺序与实际代码结构体定义中的位段顺序是相反的。

仲裁段定义

//CAN扩展帧定义
typedef struct {
    MX_U32 ibDDevID   : 5; //目标设备
    MX_U32 ibDDevType : 4; //目标设备
    MX_U32 ibMsgF     : 5; //消息功能
    MX_U32 ibMsgType  : 4; //消息类型
    MX_U32 ibSDevID   : 5; //源设备
    MX_U32 ibSDevType : 4; //源设备
    MX_U32 ibMsgC     : 2; //消息优先级
    MX_U32 ibReserve  : 3; //占位 //高位
} TCanExtID; //29bit

//CAN标准帧定义
typedef struct {
    MX_U32 ibSDevID   : 5;   //源设备标识
    MX_U32 ibSDevType : 4;   //源设备类型
    MX_U32 ibMsgC     : 2;   //消息优先级 /MsgTF定义在数据段中
    MX_U32 ibReserve  : 21;  //占位 //高位
} TCanStdID;

数据段定义

//数据段定义举例
typedef struct tagPtlCanDemoData {
    MX_U64 ibAck1       : 1;  
    MX_U64 ibAck2       : 1;  
    MX_U64 ibDtBlockSn  : 4;  
    MX_U64 ibDtBlockLen : 18;  
    MX_U64 u8FileType   : 8;   
    MX_U64 u8FileVer    : 8;  
    MX_U64 u8CheckSum   : 8;  
    MX_U64 ibMsTmout    : 10;   
    MX_U64 ibReserve    : 6;   //保留
} TCanDenoDt;

跨平台CAN通信

在《网络编程/在哪些场景中不必要进行网络字节序转换》中我们谈及了跨平台的以太网通信,结合《存储和传输/探究结构数据(C/C++结构体)在内存中的对齐和填充规则》 中的讲述,我们知道,即使通信双方的平台不一致,其协议层次上的结构类型的定义也是一致的,且必须是完全一致的,包括字段类型和字段定义的顺序。所谓的字节序转换,仅仅是在传输的前一刻或者读取共享文件的后一刻,根据实际情况转换结构的具体多字节字段,或者如文本文件BOM规则和编码规则进行正确解析。

如果真的碰上了CAN通信双方系统的字节序模式不一致,那么CAN协议层次上的数据(位段)结构定义,也是要双方一致的。结合《语言基础/分析和实践 C&C++ 位域结构数据类型》文中的分析可知,无论是在小端系统还是大端系统中,位域结构体中的末尾位段(最后出现的位段),都对应存储字段(也是传输字段)的高位区域,末尾字段的最高有效位对应着整个存储字段的最高有效位。我猜测,即使CAN通信双方字节序模式不一致,发收双发的标识符寄存器和数据寄存器的位值应该会保持一致,并期望在CAN驱动层会替我映射符合本机的大小端字节序。

就这样吧,洗洗睡了。

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

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

相关文章

全自动监控主机掉线或录像回放异常

支持浏览器的cookie或者密码进行登录监控主机。 已实现的功能&#xff1a; 1、后台常驻运行 2、支持多主机监测&#xff0c;回放丢失、线路中断 3、支持异常报警通知到企业微信 监控主机已经自带监控异常邮箱报警通知的功能了&#xff0c;为什么还要额外做呢&#xff1f; 1、…

数学生物学-4-混乱系统(Chaotic Systems)

混沌系统&#xff08;Chaotic Systems&#xff09;是一种复杂且非线性的动态系统&#xff0c;其基本概念和特征有&#xff1a; 对初始条件的敏感依赖性&#xff1a; 混沌系统的核心特征之一是对初始条件的极端敏感性&#xff0c;这一特性通常被称为“蝴蝶效应”。这意味着即使是…

NVIDIA超分辨率开启详细教程

NVIDIA超分辨率介绍 参考链接&#xff1a;NVIDIA RTX 视频超分辨率NVIDIA 目前推出的 RTX 视频超分辨率 (VSR) 更新可实现更出色的整体图形保真度&#xff0c;同时保留精美细节&#xff0c;提供对原生视频的上采样技术&#xff0c;并且还支持 GeForce RTX 20 系列 GPU。 RTX V…

unity Standard Assests资源商店无法安装解决方案

Unity游戏开发 “好读书&#xff0c;不求甚解&#xff1b;每有会意&#xff0c;便欣然忘食。” 本文目录&#xff1a; Unity游戏开发 Unity游戏开发unity中国 嗨嗨嗨&#xff0c;我来啦。 这几天的川渝之旅已经圆满结束了&#xff0c;今天开始正常给大家更新&#xff1a; Unity…

素数筛选(暴力排除)

前言&#xff1a;写这一题的时候没看到本质&#xff0c;只拿了一半的分&#xff0c;其实这一题就是找最小匹配的素数 而且我还忘记去重导致一半的样例没过 题目地址 法一&#xff1a;直接先去重&#xff0c;后对每一个数作为因子&#xff0c;开一个桶记录 #include<iostre…

前端css溢出属性overflow

overflow: visible;默认是显示溢出 overflow:hidden;溢出隐藏 overflow: scroll; 出现滚动条&#xff0c;就算内容不超过盒子都显示滚动条 overflow: auto;内容多了自动出现滚动条 overflow: inherit;继承父元素效果 <!DOCTYPE html> <html lang"en"&…

推荐 3 款最好用的流程图工具,简单易用,总有一款适合你

Dia Dia是一款开源的流程图绘制软件&#xff0c;是GNU计划的一部分&#xff0c;由Alexander Larsson创立。该软件使用单文档界面&#xff08;CSDI&#xff09;模式&#xff0c;类似于GIMP&#xff0c;并且设计上将多种需求以模块化的方式进行处理&#xff0c;如流程图、网络图、…

【JVM】JVM内存模型与操作系统内存模型(二)

JVM内存模型与操作系统内存模型 本地方法栈 与虚拟机栈发挥的作用是相似的&#xff0c;他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务&#xff0c;而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中使用、使用方式与数据结…

7.添加购物车以及完善导航条跳转

添加购物车 一、引言二、前端2.1引入轻组件2.2为图片添加点击事件2.3创建addShoppingCart函数 三、后端四、运行效果五、导航条跳转 一、引言 在前几篇文章中&#xff0c;我们一步一步慢慢的实现了项目的建立&#xff0c;从数据库获取数据显示在页面&#xff0c;商品大图查看&…

08 内置函数

目录 日期函数字符串函数数学函数其他函数 1. 日期函数 函数名称描述current_date&#xff08;&#xff09;当前日期current_time&#xff08;&#xff09;当前时间current_timestamp&#xff08;&#xff09;当前时间戳date(datetime)返回datetime参数的日期部分date_add(da…

Java 继承Thread 和 实现Runnable的关联

在java中创建线程的最常用的两种方法 1.继承Thread类 2.继承Runnable接口 但是实现了Runnable接口之后&#xff0c;这个类不能自己启动&#xff0c;需要将其传递给一个Thread实例对象&#xff0c;然后通过Thread对象的start() 方法进行启动&#xff0c;因为只有Thread类中的 …

【系统架构设计】软件架构设计(1)

【系统架构设计】软件架构设计&#xff08;1&#xff09; 软件架构概述架构需求与软件质量属性软件架构风格数据流风格批处理序列管道-过滤器2者风格比较 仓库风格--黑板系统 层次系统架构风格二层及三层C/S架构风格MVCMVP 面向服务的架构 软件架构概述 基于架构的软件开发模型…

无心剑中译莎士比亚《吾爱稀罕胜天仙》

莎士比亚十四行诗第130首 Sonnet 130 吾爱稀罕胜天仙 My mistress’ eyes are nothing like the sun; Coral is far more red than her lips’ red; If snow be white, why then her breasts are dun; If hairs be wires, black wires grow on her head. I have seen roses d…

C++相关概念和易错语法(29)(lambda、function、bind)

1.lambda lamba表达式本质是匿名函数 书写格式&#xff1a;[ 捕捉列表 ] ( 参数 ) mutable -> 返回值 { 函数体 } 下面我会由简到难分享lambda的用法和它的理解 &#xff08;1&#xff09;基本使用和理解 我们先来看一段代码 我认为第一次看这个表达式应该还是能够…

LeetCode.55.跳跃游戏(贪心算法思路)

题目描述&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 输…

为什么大公司不愿意使用 Python 作为 Web 后端开发语言?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

使用uart串口配置TMC2209模块

串口配置的优点&#xff1a; 通过串口助手配置TMC2209的寄存器实现转速&#xff0c;方向&#xff0c;细分数等寄存器设置。最大细分可达256。 TMC2209串口配置数据发送格式&#xff1a; 通过数据手册可知&#xff0c;TMC2209写入数据需要发送64位16进制。 前面0~7位是同步保…

OpenStack云计算平台实战

项目一 任务一 了解云计算 目前主流的开源云计算平台如下&#xff1a; OpenStack。OpenStack是一个提供IAAS开源解决方案的全球性项目&#xff0c;由Rackspace公司和NASA共同创办&#xff0c;采用了Apache2.0许可证&#xff0c;可以随意使用。OpenStack并不要求使用专门的硬…

“走进孔子博物馆 赓续千年乐章”孔乐古筝专场音乐会圆满成功!

2024年8月11日下午&#xff0c;备受期待的“走进孔子博物馆 赓续千年乐章”孔乐古筝专场音乐会在孔子博物馆隆重举行。此次音乐会不仅吸引了众多古筝艺术爱好者前来观看&#xff0c;更是一次将中华传统文化精髓与现代艺术相结合的盛会。现场座无虚席&#xff0c;观众朋友们共同…

C语言基础(十三)

指针的用法&#xff1a; 测试代码1&#xff1a; #include "date.h" #include <stdio.h> #include <stdlib.h> int main() { int time getTime();int n 3, m 4; // 使用malloc函数动态分配n行的指针数组&#xff0c;并将其赋值给二级指针arr。…