在后续配置CAN通信速率及回环模式等操作时,需要使用 ip 命令。但使用 PetaLinux 默认编译生成的 ip 命令时,会出现如下错误提示:
ip: either "dev" is duplicate, or "type" is garbage
根据参考资料的说明,需要在 rootfs 中正确配置 iproute2 组件,以获得完整功能的 ip 命令支持:
xpetalinux-config -c rootfs
#In the menuconfig for the root filesystem, navigate to the location below and add iproute2:Filesystem Packages ---> base ---> iproute2 ---> [*] iproute2参考资料:
65243 - PetaLinux 2014.4 - The "ip" Command Fails to Properly Locate CAN Devices
在上一步‘petalinux-config -c rootfs’完成之后重新使用以下命令进行编译和打包:
xxxxxxxxxxpetalinux-build
petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --u-boot --fpga --force清除之前TF卡第二分区的内容,通过以下命令重新将./image/rootfs.tar.gz文件解压到TF卡的第二个分区:
xxxxxxxxxxsudo tar -xzf ./images/linux/rootfs.tar.gz -C /media/mind/rootfs/sync将TF卡插入板卡内,跳线帽选择SD卡启动模式,板卡连接好串口并上电。
右击src建立新的文件夹:
建立相关的.c .h文件后如下图:
添加包含.h的文件夹到路径中:
通过命令将CAN通道打开并设置好波特率,相关命令如下:
xxxxxxxxxx #查看网络设备中有无增加了can0: ifconfig -a #设置can0的波特率,这里设置为100kbps ip link set can0 type can bitrate 100000 #设置完成后可以通过以下命令查询can0设备的参数 ip -details link show can0 #当设置完成后,可以使用以下命令使能can0设备 ifconfig can0 up #使用以下命令关闭can0设备 ifconfig can0 down #在设备工作中,可以使用下面的命令来查询工作状态 ip -d -s link show can0 #设置can0为回环模式,自发自收 ip link set can0 up type can loopback on
#波特率和模式可以一起设置 ip link set can0 type can bitrate 200000 loopback on ip link set can0 type can bitrate 200000 loopback off #-e 表示扩展帧,CAN_ID最大29bit,标准帧CAN_ID最大11bit ,-i表示CAN_ID # 0x800对应帧ID,0x11~0x88对应发送的数据 cansend can0 -i 0x800 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 -e #--loop 表示发送20个包 cansend can0 -i 0x02 0x11 0x12--loop=20 #接收CAN0数据 candump can0更改can的工作状态时,须先关闭can0,否则会报”Device or resource busy"
Linux系统将CAN设备作为网络设备进行管理,提供了SocketCAN接口,使得CAN总线通信可以像以太网一样,应用程序开发接口更加通用,也更灵活。
对CAN初始化和设置,是通过system()系统调用前面提到的相应的命令来完成。
xxxxxxxxxxbool can_open(CAN_Handle_t *h, const char *ifname){ uint8_t cmd_cfg[64]; int ret = -1;
can_close(h); can_is_up = false; can_send_active = false; can_rcv_status = false;
sprintf(cmd_cfg, "ip link set can0 type can bitrate %d loopback %s", can_baud_rate, (can_mode == 0) ?"off":"on"); ret = system(cmd_cfg);
if (ret != -1) { ret = system("ip link set can0 up"); }
if (ret == -1) { perror("open can interface failed"); return false; }
struct ifreq ifr; struct sockaddr_can addr;
if (!h || !ifname) return false;
h->sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (h->sockfd < 0) { perror("socket(PF_CAN)"); return false; }
strncpy(h->ifname, ifname, sizeof(h->ifname) - 1);
memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); if (ioctl(h->sockfd, SIOCGIFINDEX, &ifr) < 0) { perror("ioctl(SIOCGIFINDEX)"); close(h->sockfd); h->sockfd = -1; return false; }
addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex;
if (bind(h->sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(h->sockfd); h->sockfd = -1; return false; }
can_is_up = true; can_send_active = false; can_rcv_status = false; return true;}
void can_close(CAN_Handle_t *h){ const char *cmd = {"ip link set can0 down"}; int ret = system(cmd); if (ret == -1) { perror("system"); }
if (h && h->sockfd >= 0) { close(h->sockfd); h->sockfd= -1; }}SocketCAN中大部分的数据结构和函数定义在linux/can.h中,CAN总线套接字的创建采用标准的网络套接字来完成。
CAN总线每次接收数据都是以can_frame为单位,该结构体定义如下:
xxxxxxxxxxstruct canfd_frame { canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ __u8 len; /* frame payload length in byte */ __u8 flags; /* additional flags for CAN FD */ __u8 __res0; /* reserved / padding */ __u8 __res1; /* reserved / padding */ __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));};can_id为帧的标识符,如果发送的是标准帧,就使用can_id的低11位;如果为扩展帧,就是用0~28位。can_id的低29,30,31位是帧的标识位,用来定义帧的类型,如下所示:
xxxxxxxxxx/* special address description flags for the CAN_ID *//* EFF/SFF is set in the MSB *//* remote transmission request *//* error message frame */数据发送使用write函数实现,发送数据帧标识符为0x123,发送的数据为一个字节的0xAB,发送方法如下:
xxxxxxxxxxstruct can_frame frame;frame.can_id = 0x123;frame.can_dlc = 1;frame.data[0] = 0xAB;int nbytes = write(s, &frame, sizeof(frame));if(nbytes != sizeof(frame)) printf("Error\n");如果发送的是远程帧,则frame.can_id = CAN_RTR_FLAG | 0x123
数据接收使用read函数来完成,实现如下:
xxxxxxxxxxstruct can_frame frame;int nbytes = read(s, &frame, sizeof(frame))在 Linux 下测试 CAN 通信程序 通常需要创建一个线程负责接收,通过线程的使用能充分利用 Linux 内核提供的 任务调度与资源复用。
xxxxxxxxxxpthread_t recv_tid;pthread_create(&recv_tid, NULL, can_recv_thread, &g_can_handle);
void *can_recv_thread(void *arg){ CAN_Handle_t *h = (CAN_Handle_t *)arg; uint32_t id = 0;
while (1) { if (can_is_up && (can_recv(h, &id, can_rcv_buf, &can_rcv_len) > 0)) { printf("Received CAN frame: ID=0x%X, Data=", id); for (int i = 0; i < can_rcv_len; i++) { printf("%02X ", can_rcv_buf[i]); } can_rcv_status = true; printf("\n"); } else { usleep(1000); } }
printf("CAN receive thread exiting.\n"); return NULL;}