推荐一个CAN波特率计算器CAN波特率计算 f103AHP1_36M f407AHP1_42M 采样点软件有说明.rar2.2 CAN 发送邮箱回到图 中的 CAN 外设框图,在标号处的是 CAN 外设的发送邮箱,它一共有 3 个发送邮箱,即最多可以缓存 3 个待发送的报文每个发送邮箱中包含有标识符寄存器 CAN_TIxR、数据长度控制寄存器 CAN_TDTxR 及 2 个数据寄存器 CAN_TDLxR、CAN_TDHxR,它们的功能见表当我们要使用 CAN 外设发送报文时,把报文的各个段分解,按位置写入到这些寄存器中,并对标识符寄存器 CAN_TIxR 中的发送请求寄存器位 TMIDxR_TXRQ 置 1,即可把数据发送出去其中标识符寄存器 CAN_TIxR 中的 STDID 寄存器位比较特别我们知道 CAN 的标准标识符的总位数为 11 位,而扩展标识符的总位数为 29 位的当报文使用扩展标识符的时候,标识符寄存器 CAN_TIxR 中的 STDID[10:0] 等效于 EXTID[18:28] 位,它与 EXTID[17:0] 共同组成完整的 29位扩展标识符2.3 CAN 接收 FIFO图 中的 CAN 外设框图,在标号处的是 CAN 外设的接收 FIFO,它一共有 2 个接收 FIFO,每个 FIFO 中有 3 个邮箱,即最多可以缓存 6 个接收到的报文当接收到报文时,FIFO 的报文计数器会自增,而 STM32 内部读取 FIFO 数据之后,报文计数器会自减,我们通过状态寄存器可获知报文计数器的值,而通过前面主控制寄存器的 RFLM 位,可设置锁定模式,锁定模式下 FIFO溢出时会丢弃新报文,非锁定模式下 FIFO 溢出时新报文会覆盖旧报文跟发送邮箱类似,每个接收 FIFO 中包含有标识符寄存器 CAN_RIxR、数据长度控制寄存器CAN_RDTxR 及 2 个数据寄存器 CAN_RDLxR、CAN_RDHxR,它们的功能见表通过中断或状态寄存器知道接收 FIFO 有数据后,我们再读取这些寄存器的值即可把接收到的报文加载到 STM32 的内存中2.4 验收筛选器图 中的 CAN 外设框图,在标号处的是 CAN 外设的验收筛选器,一共有 28 个筛选器组,每个筛选器组有 2 个寄存器,CAN1 和 CAN2 共用的筛选器的在 CAN 协议中,消息的标识符与节点地址无关,但与消息内容有关因此,发送节点将报文广播给所有接收器时,接收节点会根据报文标识符的值来确定软件是否需要该消息,为了简化软件的工作,STM32 的 CAN 外设接收报文前会先使用验收筛选器检查,只接收需要的报文到 FIFO中筛选器工作的时候,可以调整筛选 ID 的长度及过滤模式根据筛选 ID 长度来分类有有以下两种:(1) 检查 STDID[10:0]、EXTID[17:0]、IDE 和 RTR 位,一共 31 位(2) 检查 STDID[10:0]、RTR、IDE 和 EXTID[17:15],一共 16 位通过配置筛选尺度寄存器 CAN_FS1R 的 FSCx 位可以设置筛选器工作在哪个尺度而根据过滤的方法分为以下两种模式:(1) 标识符列表模式,它把要接收报文的 ID 列成一个表,要求报文 ID 与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理(2) 掩码模式,它把可接收报文 ID 的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码 (关键字) 相同,就符合要求,报文就会被保存到接收 FIFO通过配置筛选模式寄存器 CAN_FM1R 的 FBMx 位可以设置筛选器工作在哪个模式不同的尺度和不同的过滤方法可使筛选器工作在图 的 4 种状态每组筛选器包含 2 个 32 位的寄存器,分别为 CAN_FxR1 和 CAN_FxR2,它们用来存储要筛选的ID 或掩码,各个寄存器位代表的意义与图中两个寄存器下面“映射”的一栏一致,各个模式的说明见表例如下面的表格所示,在掩码模式时,第一个寄存器存储要筛选的 ID,第二个寄存器存储掩码,掩码为 1 的部分表示该位必须与 ID 中的内容一致,筛选的结果为表中第三行的 ID 值,它是一组包含多个的 ID 值,其中 x 表示该位可以为 1 可以为 0而工作在标识符模式时,2 个寄存器存储的都是要筛选的 ID,它只包含 2 个要筛选的 ID 值 (32位模式时)如果使能了筛选器,且报文的 ID 与所有筛选器的配置都不匹配,CAN 外设会丢弃该报文,不存入接收 FIFO2.5 整体控制逻辑回到图 结构框图,图中的标号处表示的是 CAN2 外设的结构,它与 CAN1 外设是一样的,他们共用筛选器且由于存储访问控制器由 CAN1 控制,所以要使用 CAN2 的时候必须要使能CAN1 的时钟其中 STM32F103 系列芯片不具有 CAN2 控制器2.6 STM32 HAL库代码逻辑2.6.1 初始化注意:网络上基本上用的很久的HAL库,我们采用很新的1.25.2,最新的库还是差异挺大的
从 STM32 的 CAN 外设我们了解到它的功能非常多,控制涉及的寄存器也非常丰富,而使用STM32 HAL 库提供的各种结构体及库函数可以简化这些控制过程跟其它外设一样,STM32HAL 库提供了 CAN 初始化结构体及初始化函数来控制 CAN 的工作方式,提供了收发报文使用的结构体及收发函数,还有配置控制筛选器模式及 ID 的结构体这些内容都定义在库文件“STM32F4xx_hal_can.h”及“STM32F4xx_hal_can.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档首先我们来学习初始化结构体的内容,见代码清单 1代码清单 CAN 初始化结构
typedef struct{ uint32_t Prescaler; / 配置 CAN 外设的时钟分频,可设置为 1-1024/ uint32_t Mode; / 配置 CAN 的工作模式,回环或正常模式 / uint32_t SyncJumpWidth; / 配置 SJW 极限值 / uint32_t TimeSeg1; / 配置 BS1 段长度 / uint32_t TimeSeg2; / 配置 BS2 段长度 / FunctionalState TimeTriggeredMode; / 是否使能 TTCM 时间触发功能 / FunctionalState AutoBusOff; / 是否使能 ABOM 自动离线管理功能 / FunctionalState AutoWakeUp; / 是否使能 AWUM 自动唤醒功能 / FunctionalState AutoRetransmission; / 是否使能 NART 自动重传功能 / FunctionalState ReceiveFifoLocked; / 是否使能 RFLM 锁定 FIFO 功能 / FunctionalState TransmitFifoPriority;/ 配置 TXFP 报文优先级的判定方法 /} CAN_InitTypeDef;
体这些结构体成员说明如下,其中括号内的文字是对应参数在 STM32 HAL 库中定义的宏(1) Prescaler本成员设置 CAN 外设的时钟分频,它可控制时间片 Tq 的时间长度,这里设置的值最终会减 1 后再写入 BRP 寄存器位,即前面介绍的 Tq 计算公式:Tq = (BRP[9:0]+1) x TPCLK等效于:Tq = CAN_Prescaler x TPCLK(2) Mode本成员设置 CAN 的工作模式,可设置为正常模式 (CAN_MODE_NORMAL)、回环模式 (CAN_MODE_LOOPBACK)、静默模式 (CAN_MODE_SILENT) 以及回环静默模式(CAN_MODE_SILENT_LOOPBACK)(3) SyncJumpWidth本成员可以配置 SJW 的极限长度,即 CAN 重新同步时单次可增加或缩短的最大长度,它可以被配置为 1-4Tq(CAN_SJW_1/2/3/4tq)(4) TimeSeg1本成员用于设置 CAN 位时序中的 BS1 段的长度,它可以被配置为 1-16 个 Tq 长度(CAN_BS1_1/2/3…16tq)(5) TimeSeg2本成员用于设置 CAN 位时序中的 BS2 段的长度,它可以被配置为 1-8 个 Tq 长度(CAN_BS2_1/2/3…8tq)SYNC_SEG、 BS1 段及 BS2 段的长度加起来即一个数据位的长度,即前面介绍的原来计算公式:T1bit =1Tq+TS1+TS2=1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)等效于:T1bit= 1Tq+CAN_BS1+CAN_BS2(6) TimeTriggeredMode本成员用于设置是否使用时间触发功能 (ENABLE/DISABLE),时间触发功能在某些CAN 标准中会使用到(7) AutoBusOff本成员用于设置是否使用自动离线管理 (ENABLE/DISABLE),使用自动离线管理可以在节点出错离线后适时自动恢复,不需要软件干预(8) AutoWakeUp本成员用于设置是否使用自动唤醒功能 (ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒(9) AutoWakeUp本成员用于设置是否使用自动离线管理功能 (ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预(10) AutoRetransmission本成员用于设置是否使用自动重传功能 (ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止(11) ReceiveFifoLocked本成员用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收 FIFO 后,若FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据(12) TransmitFifoPriority本成员用于设置发送报文的优先级判定方法 (ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送配置完这些结构体成员后,我们调用库函数 HAL_CAN_Init 即可把这些参数写入到 CAN 控制寄存器中,实现 CAN 的初始化2.6.2 CAN 发送及接收结构体在发送或接收报文时,需要往发送邮箱中写入报文信息或从接收 FIFO 中读取报文信息,利用STM32HAL 库的发送及接收结构体可以方便地完成这样的工作,它们的定义见代码清单 代码清单 39‑2 CAN 发送及接收结构体typedef struct{ uint32_t StdId; / 存储报文的标准标识符 11 位,0-0x7FF. / uint32_t ExtId; / 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. / uint32_t IDE; / 存储 IDE 扩展标志 / uint32_t RTR; / 存储 RTR 远程帧标志 / uint32_t DLC; / 存储报文数据段的长度,0-8 / FunctionalState TransmitGlobalTime; } CAN_TxHeaderTypeDef;typedef struct{ uint32_t StdId; / 存储报文的标准标识符 11 位,0-0x7FF. / uint32_t ExtId; / 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. / uint32_t IDE; / 存储 IDE 扩展标志 / uint32_t RTR; / 存储 RTR 远程帧标志 / uint32_t DLC; / 存储报文数据段的长度,0-8 / uint32_t Timestamp; uint32_t FilterMatchIndex; } CAN_RxHeaderTypeDef;
这些结构体成员, 说明如下:(1) StdId本成员存储的是报文的 11 位标准标识符,范围是 0-0x7FF(2) ExtId本成员存储的是报文的 29 位扩展标识符,范围是 0-0x1FFFFFFFExtId 与 StdId 这两个成员根据下面的 IDE 位配置,只有一个是有效的(3) IDE本成员存储的是扩展标志 IDE 位,当它的值为宏 CAN_ID_STD 时表示本报文是标准帧,使用 StdId 成员存储报文 ID;当它的值为宏 CAN_ID_EXT 时表示本报文是扩展帧,使用 ExtId 成员存储报文 ID(4) RTR本成员存储的是报文类型标志 RTR 位,当它的值为宏 CAN_RTR_Data 时表示本报文是数据帧;当它的值为宏 CAN_RTR_Remote 时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,数据是无效的(5) DLC本成员存储的是数据帧数据段的长度,它的值的范围是 0-8,当报文是遥控帧时 DLC值为 02.6.3 CAN 筛选器结构体CAN 的筛选器有多种工作模式,利用筛选器结构体可方便配置,它的定义见代码清单 代码清单CAN 筛选器结构体typedef struct{ uint32_t FilterIdHigh; /CAN_FxR1 寄存器的高 16 位 / uint32_t FilterIdLow; /CAN_FxR1 寄存器的低 16 位 / uint32_t FilterMaskIdHigh; /CAN_FxR2 寄存器的高 16 位 / uint32_t FilterMaskIdLow; /CAN_FxR2 寄存器的低 16 位 / uint32_t FilterFIFOAssignment; / 设置经过筛选后数据存储到哪个接收 FIFO / uint32_t FilterBank; / 筛选器编号,范围 0-27,数据手册上说0-27是CAN1/CAN2共享,但是实测发现并不是这样,CAN1是0-13,CAN2是14-27 / uint32_t FilterMode; / 筛选器模式 / uint32_t FilterScale; / 设置筛选器的尺度 / uint32_t FilterActivation; / 是否使能本筛选器 / uint32_t SlaveStartFilterBank; } CAN_FilterTypeDef;
这些结构体成员都是“41.2.14 验收筛选器”小节介绍的内容,可对比阅读,各个结构体成员的介绍如下:(1) FilterIdHighFilterIdHigh 成员用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的高 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID(2) FilterIdLow类似地,FilterIdLow 成员也是用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的低 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID(3) FilterMaskIdHighFilterMaskIdHigh 存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdHigh 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdHigh 成员对应的掩码,与 FilterIdLow 组成一组筛选器(4) FilterMaskIdLow类似地, FilterMaskIdLow 存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdLow 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdLow 成员对应的掩码,与 FilterIdLow 组成一组筛选器上面四个结构体的存储的内容很容易让人糊涂,请结合前面的图 39_0_15 和下面的表 39‑7 理解,如果还搞不清楚,再结合库函数 FilterInit 的源码来分析表不同模式下各结构体成员的内容对这些结构体成员赋值的时候,还要注意寄存器位的映射,即注意哪部分代表 STID,哪部分代表 EXID 以及 IDE、RTR 位(5) FilterFIFOAssignment本成员用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收 FIFO,它的可选值为 FIFO0 或 FIFO1(宏 CAN_FILTER_FIFO0/1)(6) FilterBank本成员用于设置筛选器的编号,即本过滤器结构体配置的是哪一组筛选器,CAN 一共有 28 个筛选器,所以它的可输入参数范围为 0-27(7) FilterMode本 成 员 用 于 设 置 筛 选 器 的 工 作 模 式, 可 以 设 置 为 列 表 模 式 (宏CAN_FILTERMODE_IDLIST) 及掩码模式 (宏 CAN_FILTERMODE_IDMASK)(8) FilterScale本成员用于设置筛选器的尺度,可以设置为 32 位长 (宏 CAN_FILTERSCALE_32BIT)及 16 位长 (宏 CAN_FILTERSCALE_16BIT)(9) FilterActivation本成员用于设置是否激活这个筛选器 (宏 ENABLE/DISABLE)三、CAN Cubemx配置我们通过问题来熟悉下cubemx配置,你熟悉了这些问题基本就知道怎么配置了问题:Parameter Settings分别都是设置什么的?答案:如图问题:怎么配置波特率呢?答案:用我上面贴的工具(CAN波特率计算 f103AHP1_36M f407AHP1_42M 采样点软件有说明.rar)直接配置,举两个个例子例子1:我们要配置成500KHz,那么我们这样配置我们用采集点为80%,所以BS1为4tq,BS2为2tq,分频系数为12,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)CAN_Prescaler)=42M/(4+2+1)/12=500kHz例子2:我们要配置成1M Hz,那么我们这样配置我们用采集点为75%,所以BS1为3tq,BS2为2tq,分频系数为7,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)CAN_Prescaler)=42M/(3+2+1)/7=1MHz问题:Basic Parameter分别是啥意思呢?Timer Triggered Communication Mode:否使用时间触发功能 (ENABLE/DISABLE),时间触发功能在某些CAN 标准中会使用到Automatic Bus-Off Management:用于设置是否使用自动离线管理功能 (ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预Automatic Wake-Up Mode:用于设置是否使用自动唤醒功能 (ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒Automatic Retransmission:用于设置是否使用自动重传功能 (ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止Receive Fifo Locked Mode:用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收 FIFO 后,若FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据Transmit Fifo Priority:用于设置发送报文的优先级判定方法 (ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送配置完这些结构体成员后,我们调用库函数 HAL_CAN_Init 即可把这些参数写入到 CAN 控制寄存器中,实现 CAN 的初始化问题:为啥CAN分为RX0,RX1中断呢?答案:STM32有2个3级深度的接收缓冲区:FIFO0和FIFO1,每个FIFO都可以存放3个完整的报文,它们完全由硬件来管理如果是来自FIFO0的接收中断,则用CAN1_RX0_IRQn中断来处理如果是来自FIFO1的接收中断,则用CAN1_RX1_IRQn中断来处理,如图:问题:CAN SCE中断时什么?答案:status chanege error,错误和状态变化中断!四、CAN分析工具的使用下面我们会用到CAN分析工具,还是比较好用的,此部分使用作为自己使用https://www.zhcxgd.com/h-col-112.html五、实验1.Normal模式测试500K 波特率(定时发送,轮询接收)1.1 CubeMx配置1.2 设置Filter过滤,我们只使能FIFO0,并且不过滤任何消息
uint8_t bsp_can1_filter_config(void){ CAN_FilterTypeDef filter = {0}; filter.FilterActivation = ENABLE; filter.FilterMode = CAN_FILTERMODE_IDMASK; filter.FilterScale = CAN_FILTERSCALE_32BIT; filter.FilterBank = 0; filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; filter.FilterIdLow = 0; filter.FilterIdHigh = 0; filter.FilterMaskIdLow = 0; filter.FilterMaskIdHigh = 0; HAL_CAN_ConfigFilter(&hcan1, &filter); return BSP_CAN_OK;}
1.3 开启CAN(注意,默认Cubemx生成的代码并没有can start)HAL_CAN_Start(&hcan1);
1.4 编写发送函数我们开出了几个参数,id_type是扩展帧还是标准帧,basic_id标准帧ID(在标准帧中有效),ex_id扩展帧ID(在扩展帧中有效),data要发送的数据,data_len要发送的数据长度uint8_t bsp_can1_send_msg(uint32_t id_type,uint32_t basic_id,uint32_t ex_id,uint8_t data,uint32_t data_len){ uint8_t index = 0; uint32_t msg_box; uint8_t send_buf[8] = {0}; CAN_TxHeaderTypeDef send_msg_hdr; send_msg_hdr.StdId = basic_id; send_msg_hdr.ExtId = ex_id; send_msg_hdr.IDE = id_type; send_msg_hdr.RTR = CAN_RTR_DATA; send_msg_hdr.DLC = data_len; send_msg_hdr.TransmitGlobalTime = DISABLE; for(index = 0; index < data_len; index++) send_buf[index] = data[index]; HAL_CAN_AddTxMessage(&hcan1,&send_msg_hdr,send_buf,msg_box); return BSP_CAN_OK;}
我们在main函数中1s发送一帧,标准帧跟扩展帧交叉调用,代码如下:send_data[0]++;send_data[1]++;send_data[2]++;send_data[3]++;send_data[4]++;send_data[5]++;send_data[6]++;send_data[7]++;if(id_type_std == 1){ bsp_can1_send_msg(CAN_ID_STD,1,2,send_data,8); id_type_std = 0;}else{ bsp_can1_send_msg(CAN_ID_EXT,1,2,send_data,8); id_type_std = 1;}HAL_Delay(1000);
我们通过CAN协议分析仪来抓下结果1.5 编写轮询接收函数uint8_t bsp_can1_polling_recv_msg(uint32_t basic_id,uint32_t ex_id,uint8_t data,uint32_t data_len){ uint8_t index = 0; uint8_t recv_data[8]; CAN_RxHeaderTypeDef header; while (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) != 0) { if (__HAL_CAN_GET_FLAG(&hcan1, CAN_FLAG_FOV0) != RESET) printf("[CAN] FIFO0 overrun!\n"); HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &header, recv_data); if(header.IDE == CAN_ID_STD) { printf("StdId ID:%d\n",header.StdId); } else { printf("ExtId ID:%d\n",header.ExtId); } printf("CAN IDE:0x%x\n",header.IDE); printf("CAN RTR:0x%x\n",header.RTR); printf("CAN DLC:0x%x\n",header.DLC); printf("RECV DATA:"); for(index = 0; index < header.DLC; index++) { printf("0x%x ",recv_data[index]); } printf("\n"); }}
实验一总结:1.没用调用HAL_CAN_Start(&hcan1);使能CAN2.没有编写Filter函数,我开始自认为不设置就默认不过滤,现在看来是我想多了,其实想想也合理,你如果不过滤分配FIFO,STM32怎么决定把收到的放到哪个FIFO中待提升:1.目前只用到FIFO0,待把FIFO1使用起来2.Normal模式测试500K 波特率(定时发送,中断接收)2.1 CubeMx配置步骤2,3,4跟polling完全一致,我们来直接说下中断怎么用(主要是使能notifity就行了)static 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 = 12; hcan1.Init.Mode = CAN_MODE_NORMAL; hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; hcan1.Init.TimeSeg1 = CAN_BS1_4TQ; hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; hcan1.Init.TimeTriggeredMode = DISABLE; hcan1.Init.AutoBusOff = ENABLE; hcan1.Init.AutoWakeUp = ENABLE; hcan1.Init.AutoRetransmission = DISABLE; hcan1.Init.ReceiveFifoLocked = DISABLE; hcan1.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan1) != HAL_OK) { Error_Handler(); } / USER CODE BEGIN CAN1_Init 2 / bsp_can1_filter_config(); HAL_CAN_Start(&hcan1); HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING); / USER CODE END CAN1_Init 2 /}
下面我们来编写下中断函数void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef hcan){ uint8_t index = 0; uint8_t recv_data[8]; CAN_RxHeaderTypeDef header; HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &header, recv_data); if(header.IDE == CAN_ID_STD) { printf("StdId ID:%d\n",header.StdId); } else { printf("ExtId ID:%d\n",header.ExtId); } printf("CAN IDE:0x%x\n",header.IDE); printf("CAN RTR:0x%x\n",header.RTR); printf("CAN DLC:0x%x\n",header.DLC); printf("RECV DATA:"); for(index = 0; index < header.DLC; index++) { printf("0x%x ",recv_data[index]); } printf("\n");}
另外,整理了一些电子工程类的资料,分享给大家,目前有模拟电路、单片机、C语言、PCB设计、电源相关、FPGA、EMC、物联网、Linux相关学习资料,还有针对大学生的资料包,后续还会有更多资料分享给大家,助力大家学习,成就梦想~博主福利:点击链接免费获取电子工程类学习资料「链接」(图片来源网络,侵删)
0 评论