当前位置: 首页 > news >正文

编写c++程序分别在x86和arm架构的ubuntu下访问CAN 接口设备

有一台电源,支持通过CANBus协议与外部通讯。而我们项目的程序,最终是要部署到一台工控机上,但该工控机内存只有4G,内置了一个被厂家修改过的ubuntu20.04,在开发调试,又卡又不方便。我的计划是,先在我本机的虚拟机上开发,虚拟机装的也是ubuntu,ubuntu24.04,跑通了再移植到工控机。

这里面有一些步骤需要处理。尤其是对我这个以前很少接触硬件的程序员来说,有许多困难。首先第一步是要将电源与电脑连接;第二步是在虚拟机上写c++程序通过CANBus协议与电源通信;第三步是把电源改为与工控机连接;第四步把虚拟机的c++程序移植到工控机。

为什么会有这么多步骤呢,因为我的开发机只是普通的笔记本电脑,除了接显示器,基本只有USB口,如何跟电源相连是个问题,而工控机设置了许多端子,其中就有2个CAN口,可以直接自己找一些线与电源的引脚相连;其次是笔记本电脑的CPU架构是x86,而工控机是ARM,这个对使用的库有影响。

以下是详细介绍。

一、笔记本电脑上的虚拟机与电源相连

上面提到,我的笔记本电脑基本上只有USB口,它怎么跟电源连接呢?此前我对硬件几乎一片空白,厂家其实是代理商,他也一知半解,真正的厂家爱答不理。最后通过搜索研读使用手册,搜索资料,特别是请教集团嵌入式开发大牛,才有了一点思路。最后是买了一个CAN转接USB口的这么一个东西连上了。
在这里插入图片描述
在这里插入图片描述
就两根线。

二、虚拟机上的程序

CAN转接USB口是个很成熟的产品了,提供了丰富翔实的资料,和供开发的库,所以开发起来并没有太多障碍。主要就是通过CAN转接USB口与电源通讯。在我们的程序中,CAN转接USB口称为Device(设备),它上面有两个CAN口,那么就有个编号,第一个CANInd就是0,第二个就是1。程序运行的时候,首先要打开Device,然后初始化指定的CAN口,就可以给电源(实际上是给CAN转接USB口)发送指令,然后接收电源返回的数据了。

CANBus是很成熟的协议了,应用广泛,其指令有固定的格式。CAN转接USB口做了一些封装,按照其格式填写,使用很方便。摘录我的部分源代码如下:

1、CAN转接USB口提供的库

1)数据结构

// 2.定义CAN信息帧的数据类型。
typedef struct _VCI_CAN_OBJ {UINT ID;UINT TimeStamp;BYTE TimeFlag;BYTE SendType;BYTE RemoteFlag; // 是否是远程帧BYTE ExternFlag; // 是否是扩展帧BYTE DataLen;BYTE Data[8];BYTE Reserved[3];
} VCI_CAN_OBJ, *PVCI_CAN_OBJ;// 3.定义初始化CAN的数据类型
typedef struct _INIT_CONFIG {DWORD AccCode;DWORD AccMask;DWORD Reserved;UCHAR Filter;UCHAR Timing0;UCHAR Timing1;UCHAR Mode;
} VCI_INIT_CONFIG, *PVCI_INIT_CONFIG;

2)函数

#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endifEXTERN_C DWORD VCI_OpenDevice(DWORD DeviceType, DWORD DeviceInd,DWORD Reserved);
EXTERN_C DWORD VCI_CloseDevice(DWORD DeviceType, DWORD DeviceInd);
EXTERN_C DWORD VCI_InitCAN(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd,PVCI_INIT_CONFIG pInitConfig);
EXTERN_C DWORD VCI_StartCAN(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd);
EXTERN_C DWORD VCI_ResetCAN(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd);EXTERN_C ULONG VCI_Transmit(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd,PVCI_CAN_OBJ pSend, UINT Len);
EXTERN_C ULONG VCI_Receive(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd,PVCI_CAN_OBJ pReceive, UINT Len, INT WaitTime);

调用顺序就是
VCI_OpenDevice
VCI_InitCAN
VCI_StartCAN
VCI_Transmit,发送指令
VCI_Receive,接收返回数据

三、工控机与电源相连

工控机跟普通电脑稍有点不一样,本身就提供了许多插口,我们买的是一种基本款,CAN口就有2个,RS485口4个,还有许多乱七八糟的口,可能是供自定义的。所以电源跟工控机直连就可以了。问题是,我接了线之后发现无法通讯。查阅资料说,H和L两个接口间需要接一个120欧姆的电阻,以防止形成通讯回路。

怪不得,之前在用CAN转接USB口的时候,一定要将电阻开关至少打开一个才行。

在这里插入图片描述
在这里插入图片描述
我也不知道电阻是啥,请教搞嵌入式开发的同事,他一听就懂。由于没有120欧的电阻,就找了2个220欧姆的,并联接在H和L之间,说并联就是110欧,近似120欧,应该也可以。结果真的就是,接上电阻以后,马上就能收到返回数据了。我真是佩服得五体投地。
在这里插入图片描述

四、工控机上的程序

那工控机上的程序该怎么写呢?现有的程序依赖CAN转接USB口厂家提供的库,但工控机不能使用。首先是工控机并不需要接CAN转接USB口;其次工控机的CPU是ARM架构的,现有的这个库不行。

我的思路是,数据结构采用目前的,自己实现那几个与CAN操作相关的函数。在CMakeLists.txt上做区分,当系统运行在x86架构下,就用厂家的库;而在arm架构下,使用自己实现的函数。
在这里插入图片描述

1、自己实现CAN操作相关函数

#include "controlcan.h"#include <cstring>
#include <iostream>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>const int OK = 1;
const int FAIL = 0;std::string getCanName(DWORD CANInd) { return "can" + std::to_string(CANInd); }void convert_to_vci_obj(const struct can_frame *src, PVCI_CAN_OBJ dst) {// ID 处理(判断是否为扩展帧 & 远程帧)dst->ID = src->can_id;dst->ExternFlag = (src->can_id & CAN_EFF_FLAG) ? 1 : 0; // 扩展帧标志位dst->RemoteFlag = (src->can_id & CAN_RTR_FLAG) ? 1 : 0; // 远程帧标志位if (dst->ExternFlag) {dst->ID &= CAN_EFF_MASK; // 取出 29 位扩展帧 ID} else {dst->ID &= CAN_SFF_MASK; // 取出 11 位标准帧 ID}dst->DataLen = src->can_dlc;                // 数据长度memcpy(dst->Data, src->data, src->can_dlc); // 拷贝数据内容// 其他字段赋默认值或根据需要填充dst->TimeStamp = 0;                              // 如果你使用 timeval 或其他时间戳机制可填入dst->TimeFlag = 0;                               // 可选标志dst->SendType = 0;                               // 通常用于发送时控制,接收时无意义memset(dst->Reserved, 0, sizeof(dst->Reserved)); // 预留字段清零
}// 只需声明为 extern "C",防止 C++ 名称改编
extern "C" {DWORD VCI_OpenDevice(DWORD DeviceType, DWORD DeviceInd, DWORD Reserved) {int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);if (s < 0) {perror("Socket creation failed");}return s;
}DWORD VCI_CloseDevice(DWORD DeviceType, DWORD Device) {close(Device);return OK;
}// 其他函数也可以简单返回
DWORD VCI_InitCAN(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd, PVCI_INIT_CONFIG pInitConfig) {std::string ifname = getCanName(CANInd);std::string cmd_down = "ip link set " + ifname + " down";std::string cmd_set = "ip link set " + ifname + " up type can bitrate " + std::to_string(pInitConfig->Reserved);if (system(cmd_down.c_str()) != 0) {std::cerr << "[ERROR] Failed to bring down " << ifname << std::endl;return FAIL;}if (system(cmd_set.c_str()) != 0) {std::cerr << "[ERROR] Failed to set bitrate on " << ifname << std::endl;return FAIL;}std::cout << "[INFO] ✔ " << ifname << " initialized with bitrate " << pInitConfig->Reserved << " bps" << std::endl;return OK;
}DWORD VCI_StartCAN(DWORD DeviceType, DWORD Device, DWORD CANInd) {std::string ifname = getCanName(CANInd);struct ifreq ifr;strcpy(ifr.ifr_name, ifname.c_str());if (ioctl(Device, SIOCGIFINDEX, &ifr) < 0) {perror("[ERROR] Getting interface index failed");return FAIL;}struct sockaddr_can addr;addr.can_family = AF_CAN;addr.can_ifindex = ifr.ifr_ifindex;if (bind(Device, (struct sockaddr *)&addr, sizeof(addr)) < 0) {perror("[ERROR] Bind failed");return FAIL;}std::cout << "[INFO] ✔ Socket bound to " << ifname << std::endl;return OK;
}DWORD VCI_ResetCAN(DWORD DeviceType, DWORD DeviceInd, DWORD CANInd) {std::string ifname = getCanName(CANInd);std::string cmd_down = "ip link set " + ifname + " down";if (system(cmd_down.c_str()) != 0) {std::cerr << "[ERROR] Failed to bring down " << ifname << std::endl;return FAIL;}return OK;
}DWORD VCI_Transmit(DWORD DeviceType, DWORD Device, DWORD CANInd, PVCI_CAN_OBJ pSend, UINT Len) {int ok = OK;// 转换为 can_frame 格式struct can_frame frame;frame.can_id = pSend->ID; // 支持扩展帧自动识别(高位非标准)if (pSend->ExternFlag) {frame.can_id |= CAN_EFF_FLAG; // 设置扩展帧标志}frame.can_dlc = pSend->DataLen;for (int i = 0; i < frame.can_dlc; ++i) {frame.data[i] = pSend->Data[i];}// 发送帧int nbytes = write(Device, &frame, sizeof(struct can_frame));if (nbytes == -1) {ok = FAIL;perror("Write");} else {std::cout << "CAN message sent successfully." << std::endl;}return ok;
}ULONG VCI_Receive(DWORD DeviceType, DWORD Device, DWORD CANInd, PVCI_CAN_OBJ pReceive, UINT Len, INT WaitTime) {struct can_frame received_frame;int nbytes = read(Device, &received_frame, sizeof(struct can_frame));if (nbytes < 0) {perror("Read");return 0;}// 判断是否为错误帧(重点过滤)if (received_frame.can_id & CAN_ERR_FLAG) {printf("Received error frame, ignored.\n");return 0;}// 判断是否为远程帧(可选过滤)if (received_frame.can_id & CAN_RTR_FLAG) {printf("Received remote frame, ignored.\n");return 0;}convert_to_vci_obj(&received_frame, pReceive);return 1;
}
}

2、CMakeLists.txt中根据情况是加载厂家库还是自己实现的库

# 检测当前架构
if(CMAKE_SIZEOF_VOID_P EQUAL 8)if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64")set(PLATFORM_X86_64 TRUE)elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")set(PLATFORM_ARM64 TRUE)endif()
endif()set (SOURCES"UnderWtConn.cpp"  
。。。"config/ConfigManager.cpp""lib/canClient.cpp"
)
if(PLATFORM_ARM64)list(APPEND SOURCES "lib/controlcanArm.cpp") #加载自己的库
endif()
add_executable(UnderwtConn ${SOURCES})if(PLATFORM_X86_64)set(CONTROLCAN_LIB_PATH ${CMAKE_SOURCE_DIR}/lib/libcontrolcan.so) #加载厂家库
endif()

3、效果

好处就是原有代码基本不需要更改,两边都能工作。

相关文章:

  • 80%的知识库场景选择FastGPT,20%的复杂场景选择Dify
  • 设计的“第一性原理”:从Photoshop与Premiere Pro的AI革新谈起
  • 具身智能系列教程——(三)gazebo环境配置与强化学习训练
  • 【git】撤销操作
  • C# 委托(调用带引用参数的委托)
  • 链表题解——删除链表的倒数第 N 个结点【LeetCode】
  • 鸿蒙5:自定义构建函数
  • 聊聊横向移动中的实际技术点 ----- ResponderSMB
  • matlab 渐进三角网(PTD)地面滤波(基础版)
  • Django ORM 2. 模型(Model)操作
  • 机器学习7——神经网络上
  • 高频SQL50题 第九天 | 1164. 指定日期的产品价格、1204. 最后一个能进入巴士的人、1907. 按分类统计薪水
  • pytorch--模型训练的一般流程
  • 1 Studying《Computer Vision: Algorithms and Applications 2nd Edition》11-15
  • MySQL之全场景常用工具链
  • MYSQL与PostgreSQL的差异
  • (Arxiv-2025)Qwen2.5-VL 技术报告
  • mybatis-plus从入门到入土(一):快速开始
  • Embedding模型微调实战(ms-swift框架)
  • 医疗AI智能基础设施构建:向量数据库矩阵化建设流程分析