接口/总线/驱动
上位机调用CAN接口卡发送数据时,受上位机系统调度耗时的影响,实际CAN卡发送时会有时间上的误差,是否有CAN卡可以将发送定时放到设备中来完成,从而规避掉上位机的调度影响呢?本文将为大家具体分析。 使用CAN接口卡是CAN通讯领域无法避开的话题,它提供各种的接口类型,兼容多种上位机系统,简单易用的二次开发接口函数库。此外,windows平台还提供了专业的应用层协议库(DBC解析库、UDS库等),比起用ARM直接开发CAN(FD),用户使用接口卡二次开发,可以直接调用高层协议函数库,可以极大的节省应用层协议栈的开发成本。用户只需关注自己的业务逻辑即可,大大的缩短项目开发周期。如此方便的用法也产生了一个问题,接口卡必须依赖于上位机的调用,不管windows还是linux系统,非实时系统就涉及到一个延时问题——系统调度的延时。例如当上位机执行到transmit发送函数,到系统执行这个动作,驱动将buffer下发给CAN接口卡的时间。系统调度时间是不可控的,取决于多方因素:程序开发的语言,电脑的性能,CPU当前的占用率等,一般都为毫秒级误差。因此,当用户需要软件定时来发送报文时,无法保证很低的时间误差。
问
是否有办法规避上位机调度的延时?
答
方法是有的。USBCANFD提供了两种方法,一定程度上规避上位机调度的时延问题:
硬件定时发送;
队列发送。
硬件定时发送
USBCANFD 支持每通道最大 100 条定时发送列表,只需将待发送数据及周期设置到设备并使能,设备将自动进行发送。相比于 PC 端的发送,定时发送精度高,周期准。在设备进行定时发送任务时,PC 端仍可调用数据发送接口进行数据发送。软件实现方法,在ZCAN_StartCAN之后,继续通过setvalue方式将定时发送结构体下载到设备中:优点:1.周期稳定,精度100us;2.可修改报文内容随时覆盖;3.可根据需求单独对某条定时报文进行禁用操作。缺点:1.数据不是自动变化的,如涉及到内容变化,需要再次设置定时;2.不适用于非周期性的报文。ZCAN_AUTO_TRANSMIT_OBJ auto_can; //从CAN定时发送结构体生成实例
ZCANFD_AUTO_TRANSMIT_OBJ auto_canfd; //从CANFD定时发送结构体生成实例
memset(&auto_can, 0, sizeof(auto_can));
auto_can.index = 0; // 定时列表索引0
auto_can.enable = 1; // 使能此索引,每条可单独设置
auto_can.interval = 100; // 定时发送间隔100ms
get_can_frame(auto_can.obj, 0); // 构造CAN报文
prop->SetValue("1/auto_send", (const char*)&auto_can); // 设置定时发送
memset(&auto_can, 0, sizeof(auto_can));
auto_can.index = 1; // 定时列表索引1
auto_can.enable = 1; // 使能此索引,每条可单独设置
auto_can.interval = 200; // 定时发送间隔200ms
get_can_frame(auto_can.obj, 1); // 构造CAN报文
prop->SetValue("1/auto_send", (const char*)&auto_can); // 设置定时发送
memset(&auto_canfd, 0, sizeof(auto_canfd));
auto_canfd.index = 2; // 定时列表索引2
auto_canfd.enable = 1; // 使能此索引,每条可单独设置
auto_canfd.interval = 500; // 定时发送间隔500ms
get_canfd_frame(auto_canfd.obj, 2); // 构造CANFD报文
prop->SetValue("1/auto_send_canfd", (const char*)&auto_canfd); // 设置定时发送
prop->SetValue("1/apply_auto_send", "0"); // 使能定时发送
Sleep(5000); // 等待发送5s
prop->SetValue("1/clear_auto_send", "0"); // 清除定时发送
队列发送
通过队列发送,用户可以提前准备好多帧报文,设定报文之间的间隔,将准备好的报文发送给设备,设备按照预定义的帧间隔进行精准发送,通过此方式可提高发送帧之间的帧间隔精度。与定时发送相比,队列发送每帧只发送一次,需由用户不断准备报文并批量发送到设备。USBCANFD-200U先通过SetValue将设备的发送模式切换成队列发送模式。队列发送缓存大小为100帧,队列发送过程中,可以通过GetValue查询当前队列缓存的剩余空间。队列发送有两种方法实现:一种是合并发送ZCAN_TransmitData——对应发送结构体ZCANDataObj;
另一种是单通道发送ZCAN_Transmit和ZCAN_TransmitFD——对应发送结构体ZCAN_Transmit_Data和ZCAN_TransmitFD_Data。
Prop->Setvalue(“0/set_send_mode”, “1”); //USBCANFD需要切换发送模式,CANFDNET无需此步骤
…
void get_can_frame_queue(ZCANDataObj& data, int ch, canid_t id, bool is_fd, UINT delay)
{
memset(&data, 0, sizeof(data)); //初始化data结构体
data.dataType = ZCAN_DT_ZCAN_CAN_CANFD_DATA;
data.chnl = ch; //通道号
ZCANCANFDData & can_data = data.data.zcanCANFDData;
can_data.frame.can_id = MAKE_CAN_ID(id, 0, 0, 0); // CAN ID + STD/EXT + DATA/RMT
can_data.frame.len = is_fd ? 64 : 8; // 数据长度 8/64
can_data.flag.unionVal.transmitType = 0; // 正常发送
can_data.flag.unionVal.txEchoRequest = 1; // 设置发送回显
can_data.flag.unionVal.frameType = is_fd ? 1 : 0; // CAN or CANFD
can_data.flag.unionVal.txDelay = ZCAN_TX_DELAY_UNIT_MS; // 队列延时单位毫秒
can_data.timeStamp = delay; // 队列延时时间,最大值 65535
for (int i = 0; i < can_data.frame.len; ++i) { // 填充 CAN 报文 DATA
can_data.frame.data[i] = i;
}
…
Ret = ZCAN.TransmitData(device_handle, data ,len);
第二种方法ZCAN_Transmit的代码实现:
Prop->Setvalue(“0/set_send_mode”, “1”); //USBCANFD需要切换发送模式,CANFDNET无需此步骤
…
ZCAN_Transmit_Data can_data[10]={};
ZCAN_TransmitFD_Data canfd_data[10]={};
memset(& can_data, 0, sizeof(can_data)); //初始化data结构体
memset(& canfd_data, 0, sizeof(canfd_data)); //初始化data结构体
…
can_data[0].frame.can_id =0x100;
can_data[0].frame.__pad =0x80; //使能CAN帧队列发送
can_data[0].frame.__res0 =0x64; // 低位,设置100ms
can_data[0].frame.__res1 =0x00; // 高位
…
canfd_data[0].frame.can_id =0x200;
canfd_data[0].frame.flags =0x80; //使能非加速CANFD队列发送,0x81使能加速CANFD队列发送
canfd_data[0].frame.__res0 =0x64; // 低位,设置100ms
canfd_data[0].frame.__res1 =0x00; // 高位
…
ret = ZCAN.Transmit(channel_handle, can_data, 10);
ret_fd = ZCAN.TransmitFD(channel_handle, canfd_data, 10);
队列发送的优缺点:
以上两种方法分别适用不同场景,根据实际应用需求,灵活使用,可以很大程度规避上位机调度带来的时延问题,对用户的通讯起到更稳定和精准的控制。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !