CAN学习笔记21(数据传输策略)
- 2026-11-06 23:02:57
CAN学习笔记21(数据传输策略)数据传输策略有多种方法,下面列举三种典型方法。 一、 定时广播,每隔一段时间,就自动广播一个数据帧 实际项目中,很多传感器采集数据,可以设定定时自动发送,如发动机转速传感器、凸轮轴位置传感器、水温传感器、汽车轮速传感器等。根据传感器采集数据的频率,设定间隔时间,自动发送。 接收方需要配置过滤器,采集需要的数据类型进行处理。 下面的程序框架继续沿用前面几节的程序框架 示例程序任务设定 每隔100ms自动广播发送一组数据到网络中。 1. 发送端程序 main.c主程序中写定时发送程序 首先自定义结构体数组,结构体中设置4组报文数据,每组报文数据给一个初始值0x11, 0x22, 0x33, 0x44。 然后whil循环程序中,设定自增+1,然后间隔100ms自动发送 为防止程序阻塞邮箱,示例程序采用的是标志位发送指定数据 每间隔100ms发送一次, 发送数据间隔时间从Timer.c中修改,如下图所示 
如果想要每隔1秒发送一次,只需要把1000改成10000即可。其它时间等比换算就行。 MyCAN.c中修改为正常模式 
2.接收端程序 main.c接收数据,并显示在OLED中 MyCAN.c中也是修改为正常模式 
编译,发送端程序下载到设备1中,接收端程序下载到设备2中 测试 视频中,设备1不需要任何操作即可自动持续发送数组报文到CAN网络中,设备2自动接收并显示这些报文。 在实际项目中,自动广播发送数据的方式应充分考虑重要的数据能够被发送到网络中,不重要的数据自动发送频率不能过快,否则总线资源就会被占用,导致重要的数据发送延迟,甚至发送失败,这样就可能会导致项目出现莫名其妙的故障或者调试不成功的风险大大增加。 二、触发传输,触发中断发送指定数据帧 实际项目中,发送方不会主动发送数据,只有发送方内部触发了某种设定的条件,才会广播数据。适合报警信息的场景,如动力电池过温报警、发动机爆震触发信号等,或者外界设定应急按键触发设备急停等。 示例程序任务设定 按下按键,触发中断,发送一组数据后,再返回程序继续发送原来程序中的数据。 1. 发送程序 (1)main.c 主程序 自定义函数 
编写按键触发程序 2. 设备2的接收程序 在main.c中修改接收程序 查询标志位, 设备1与设备2分别编译,下载,测试 视频中,设备1按下按键1,设备1发送一组数据到CAN网络中,设备2自动接收并显示出来。 三、请求发送,接收方请求发送方发送数据帧 发送方不会主动发送指定数据,当接收方请求时,发送方才会发送数据。可以使用接收设备,按键触发遥控帧请求或者数据帧请求,然后设备1收到数据请求后,触发中断,发送指定数据帧。使用正常模式测试。 实际项目中,适合故障诊断的场景,如汽车故障诊断接口,接上网线,连接网络,向网络中的各种设备发送请求数据帧,网络中的设备发送储存在芯片中的故障码,或者发送当前的数据流。接收方接收到这些信息,以此判断汽车故障。 请求数据可以使用遥控帧,也可以使用数据帧,而且发送指定的数据帧,可以附带请求参数,这样接收方程序中识别到这个特定的数据帧,就会根据程序设定,发送匹配的数据报文,比遥控帧要更好用。 注意:在项目设计时,不要出现同时向一个设备多请求的情况,否则会导致仲裁难以判断,要避免这种情况出现。 程序编写思路: 第一步,接收指定为请求的遥控帧或数据帧,置请求标志位 第二步,根据请求的标志位,发送对应的数据帧 第三步,配置接收数据程序 1. 发送端程序 main.c中,先定义一个结构体, 然后编写自定义的结构体数据发送程序,收到的数据帧使用0x300暂时代替,实际项目中可以使用其它代号。 上面程序分2部分,第1部分是检测收到的 CAN 报文,识别触发条件,置 RequestFlag 第2部分,独立判断标志位,统一执行应答动作。 小结:主循环持续检查有没有收到 CAN 报文;收到之后判断是远程请求帧或者指定指令帧,就打上应答标记;主循环单独检测这个标记,标记有效时执行一次应答发送和屏幕刷新,实现两种请求方式共用同一套应答输出。 2. 接收端程序 在main.c中 ,先自定义请求数据帧和遥控帧基本参数, 然后在循环程序中,模拟发送数据帧和遥控帧,发送到网络中。 编译,分别把发送端程序和接收端程序下载到设备1和设备2中,测试 视频中,分别按下设备2的按键1和按键2,设备1收到设备2发送的数据帧或遥控帧后,自动向CAN网络中发送一组数据帧。设备2自动接收并显示出设备1发送的数据帧。 这种通过外接设备向网络中发送请求数据帧,网络中的设备进行判断,如果与程序匹配,则自动向网络中发送指定数据。适合检修的场景,读取指定设备的报文。 写在最后 如果实际项目比较小,网络中涉及的设备不多的话,ID号可以自己随意定,反正用的也不多。 但如果网络中的设备成百上千,而且项目很大,由不同的工程师协作完成,那么不同设备之间的ID就需要提前约定好,避免连接网络进行联调联试的时候出现ID冲突。比如CANopen就是一种通用的协议方式。 CANopen 是基于 CAN 总线的应用层协议,基础规范为 CiA 301,配套不同设备子协议(DS401 数字 IO、DS402 伺服驱动、DS404 编码器等)。 它解决裸 CAN 没有统一报文含义、参数规范、设备状态、故障管理的问题,多用于工业控制。
// CAN定时发送报文结构体:标准帧ID=0x100,数据帧,4字节数据CanTxMsg TxMsg_Timing = {.StdId = 0x100, // 标准帧ID 0x100.ExtId = 0x00000000, // 扩展ID不用,填0.IDE = CAN_Id_Standard, // 选择标准帧格式.RTR = CAN_RTR_Data, // 数据帧,不是远程请求帧.DLC = 4, // 数据长度4字节.Data = {0x11, 0x22, 0x33, 0x44} // 初始发送数据};
/*定时发送*/if (TimingFlag == 1) // 判断定时标志,定时器中断置1,代表定时时间到{TimingFlag = 0; // 先清除标志,防止重复发送TxMsg_Timing.Data[0] ++; // 报文第0字节自增TxMsg_Timing.Data[1] ++; // 报文第1字节自增TxMsg_Timing.Data[2] ++; // 报文第2字节自增TxMsg_Timing.Data[3] ++; // 报文第3字节自增MyCAN_Transmit(&TxMsg_Timing);// 调用CAN发送函数,发送ID=0x100报文// OLED显示当前定时报文4个字节的十六进制数值OLED_ShowHexNum(2, 5, TxMsg_Timing.Data[0], 2);OLED_ShowHexNum(2, 8, TxMsg_Timing.Data[1], 2);OLED_ShowHexNum(2, 11, TxMsg_Timing.Data[2], 2);OLED_ShowHexNum(2, 14, TxMsg_Timing.Data[3], 2);}


/*接收部分*/if (MyCAN_ReceiveFlag()) // 查询CAN接收标志位:判断是否成功收到一帧CAN报文{MyCAN_Receive(&RxMsg); // 将收到的CAN报文读取到RxMsg结构体中if (RxMsg.RTR == CAN_RTR_Data) // 判断收到的帧是【数据帧】,只解析数据帧,过滤远程帧{/*收到定时数据帧*/if (RxMsg.StdId == 0x100 && RxMsg.IDE == CAN_Id_Standard) // 判断标准帧ID=0x100(对端定时发送帧){OLED_ShowHexNum(2, 5, RxMsg.Data[0], 2); // OLED显示第0字节,2位十六进制OLED_ShowHexNum(2, 8, RxMsg.Data[1], 2); // OLED显示第1字节,2位十六进制OLED_ShowHexNum(2, 11, RxMsg.Data[2], 2);// OLED显示第2字节,2位十六进制OLED_ShowHexNum(2, 14, RxMsg.Data[3], 2);// OLED显示第3字节,2位十六进制}}}

关注
重播 分享 赞

/*触发发送*/KeyNum = Key_GetNum(); // 读取按键,返回按键编号,无按键返回0if (KeyNum == 1) // 如果是1号按键按下{TriggerFlag = 1; // 置触发发送标志}if (TriggerFlag == 1) // 判断按键触发标志{TriggerFlag = 0; // 清除标志TxMsg_Trigger.Data[0] ++; // 报文第0字节自增TxMsg_Trigger.Data[1] ++; // 报文第1字节自增TxMsg_Trigger.Data[2] ++; // 报文第2字节自增TxMsg_Trigger.Data[3] ++; // 报文第3字节自增MyCAN_Transmit(&TxMsg_Trigger);// 发送ID=0x200触发报文// OLED显示当前触发报文4个字节十六进制OLED_ShowHexNum(3, 5, TxMsg_Trigger.Data[0], 2);OLED_ShowHexNum(3, 8, TxMsg_Trigger.Data[1], 2);OLED_ShowHexNum(3, 11, TxMsg_Trigger.Data[2], 2);OLED_ShowHexNum(3, 14, TxMsg_Trigger.Data[3], 2);}
/*接收部分*/if (MyCAN_ReceiveFlag()) // 查询CAN接收标志位:判断是否成功收到一帧CAN报文{MyCAN_Receive(&RxMsg); // 将收到的CAN报文读取到RxMsg结构体中if (RxMsg.RTR == CAN_RTR_Data) // 判断收到的帧是【数据帧】,只解析数据帧,过滤远程帧{/*收到触发数据帧*/if (RxMsg.StdId == 0x200 && RxMsg.IDE == CAN_Id_Standard) // 判断标准帧ID=0x200(对端按键触发帧){OLED_ShowHexNum(3, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(3, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(3, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(3, 14, RxMsg.Data[3], 2);}}}
关注
重播 分享 赞
CanRxMsg RxMsg; // CAN接收报文结构体,存放接收到的一帧CAN信息(ID、RTR、数据等)/*请求发送*/if (MyCAN_ReceiveFlag()) // 查询CAN接收标志:是否收到CAN报文{MyCAN_Receive(&RxMsg); // 读取收到的CAN报文到RxMsg结构体// 判断:标准帧 + 远程请求帧RTR + ID=0x300,收到远程帧请求if (RxMsg.IDE == CAN_Id_Standard &&RxMsg.RTR == CAN_RTR_Remote &&RxMsg.StdId == 0x300){RequestFlag = 1; // 置应答标志,准备回复数据帧}// 判断:标准帧 + 数据帧 + ID=0x3FF,收到指定指令数据帧,同样触发应答if (RxMsg.IDE == CAN_Id_Standard &&RxMsg.RTR == CAN_RTR_Data &&RxMsg.StdId == 0x3FF){RequestFlag = 1; // 置应答标志}}if (RequestFlag == 1) // 判断应答标志{RequestFlag = 0; // 清除标志TxMsg_Request.Data[0] ++; // 应答报文第0字节自增TxMsg_Request.Data[1] ++; // 应答报文第1字节自增TxMsg_Request.Data[2] ++; // 应答报文第2字节自增TxMsg_Request.Data[3] ++; // 应答报文第3字节自增MyCAN_Transmit(&TxMsg_Request);// 发送ID=0x300应答报文,响应远程帧或者0x3FF指令帧// OLED显示当前应答报文4个字节十六进制OLED_ShowHexNum(4, 5, TxMsg_Request.Data[0], 2);OLED_ShowHexNum(4, 8, TxMsg_Request.Data[1], 2);OLED_ShowHexNum(4, 11, TxMsg_Request.Data[2], 2);OLED_ShowHexNum(4, 14, TxMsg_Request.Data[3], 2);}
用 MyCAN_ReceiveFlag()轮询接收,不是中断阻塞接收;收到报文才解析;
做多条件匹配,区分两种触发源:
触发源 A:CAN 远程帧(CAN 本身原生的请求应答机制) 触发源 B:自定义指令数据帧(软件自定义命令,很多工程里不用远程帧,用数据帧当指令) 只要满足任意一种触发条件,只做一件事:把 RequestFlag = 1,不在这里直接发送 CAN、不调用 OLED 显示。
标志位一旦置 1,进入应答分支,第一步立刻清零标志,保证一次触发只应答一次; 更新报文数据、CAN 发送、OLED 显示全部集中在这里; 不管是「远程帧触发」还是「0x3FF 指令帧触发」,最终走同一套应答流程,对外应答报文完全一样,做到触发入口分开、响应动作复用。
// CAN远程请求报文结构体:标准帧ID=0x300,远程帧,DLC=0,远程帧不需要携带有效数据CanTxMsg TxMsg_Request_Remote = {.StdId = 0x300, // 标准帧ID 0x300,用于向对方请求数据.ExtId = 0x00000000, // 扩展ID不使用,填0.IDE = CAN_Id_Standard, // 帧类型:标准帧.RTR = CAN_RTR_Remote, // RTR=远程请求帧(Remote Frame),用来请求对应ID节点回复数据.DLC = 0, // 远程帧数据长度固定为0,无数据段.Data = {0x00} // 远程帧Data无效,填充0即可};// CAN指令数据报文结构体:标准帧ID=0x3FF,数据帧,DLC=0,作为指令触发对方应答CanTxMsg TxMsg_Request_Data = {.StdId = 0x3FF, // 标准帧ID 0x3FF,数据指令帧.ExtId = 0x00000000, // 扩展ID不使用,填0.IDE = CAN_Id_Standard, // 帧类型:标准帧.RTR = CAN_RTR_Data, // RTR=数据帧(Data Frame),携带数据的普通CAN帧.DLC = 0, // 本帧数据长度为0.Data = {0x00} // 数据段填充0};
分别使用KEY1和KEY2按键进行发送
/*请求部分*/KeyNum = Key_GetNum(); // 扫描读取按键,返回按键编号,无按键按下返回0if (KeyNum == 1) // 判断1号按键按下{MyCAN_Transmit(&TxMsg_Request_Remote); // 发送0x300远程请求帧,请求对端节点返回数据}if (KeyNum == 2) // 判断2号按键按下{MyCAN_Transmit(&TxMsg_Request_Data); // 发送0x3FF数据指令帧,触发对端节点应答}
然后读取发送端设备1发送的数据,并显示出来。
/*接收部分*/if (MyCAN_ReceiveFlag()) // 查询CAN接收标志位:判断是否成功收到一帧CAN报文{MyCAN_Receive(&RxMsg); // 将收到的CAN报文读取到RxMsg结构体中if (RxMsg.RTR == CAN_RTR_Data) // 判断收到的帧是【数据帧】,只解析数据帧,过滤远程帧{/*收到请求数据帧*/if (RxMsg.StdId == 0x300 && RxMsg.IDE == CAN_Id_Standard) // 判断标准帧ID=0x300(对端应答返回帧){OLED_ShowHexNum(4, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(4, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(2, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(4, 14, RxMsg.Data[3], 2);}}}
关注
重播 分享 赞
通过网盘分享的文件:CAN学习笔记21(数据传输策略).zip
链接: https://pan.baidu.com/s/19fiHK42SAdLrbgNvQYSpUA?pwd=5ent 提取码: 5ent
本文来自网友投稿或网络内容,如有侵犯您的权益请联系我们删除,联系邮箱:wyl860211@qq.com 。