本帖最后由 LINUX课程 于 2024-9-11 10:47 编辑
软件版本:vitis2021.1(vivado2021.1) 操作系统:WIN10 64bit 硬件平台:适用XILINX Z7/ZU系列FPGA
1 概述 ZYNQ MPSOC的PS中包含了2个CAN接口,当我们需要更多CAN接口的时候,可以使用PL的AXI-CAN IP实现扩展。在做本文之前,建议读者先了解下CAN协议,前面 “13CAN总线通信实验”中,有详细描述,这里不再重复。本文的重点是对AXI-CAN IP的使用。 实验目的: - 熟悉AXI-CAN控制的使用
- 通过VIVADO搭建axi_can的SOC工程
- 使用VITIS-SDK编写axi_can测试程序,实现类似PS-CAN测试程序功能
2 系统框图 3 AXI-CAN IP概述 以下是AXI-CAN功能框图。AXI-CAN IP通过AXI-Lite Slave总线接到处理器中。IP-CORE的功能模块包括数据收发模块、寄存器配置模块、接收过流模块、发送协议引擎,其中发送协议引擎又包括了流处理器和位计时模块。 3.1 特性 -符合ISO 11898 -1、CAN 2.0A、CAN 2.0B标准 -支持标准(11位标识符)和扩展(29位标识符)帧 -支持高达1mb /s的比特率 -发送消息FIFO,深度最多可配置64条消息 -通过一个高优先级的传输缓冲区进行传输优先级 -具有错误或仲裁损失自动重传功能 -接收消息FIFO,深度最多可达64条消息 -支持最多四个接受过滤器的接受过滤 -支持睡眠与唤醒模式 -支持Loop Back模式用于诊断 -可屏蔽错误和状态中断 -可读的错误计数器 3.2 寄存器概述 1:SRR软件复位寄存器 写入‘1’到软件复位寄存器(SRR)将CAN控制器置于配置模式。当处于配置模式时,CAN控制器在总线上不传输或接收消息。在上电期间,CEN和SRST位是'0',状态寄存器(SR)中的配置位是'1'。只有当SRR寄存器中的CEN位为'0 '时,才能更改传输层配置寄存器。 2:MSR模式选择寄存器 写入到模式选择寄存器(MSR)使CAN控制器可以进入睡眠、回循环或正常模式。在正常模式下,CAN控制器参与正常总线通信。如果SLEEP位设置为“1”,则CAN控制器进入SLEEP模式。如果LBACK位设置为'1 ',则CAN控制器进入回环模式。LBACK和SLEEP位不应该同时为“1”。在任何给定的点,CAN控制器可以处于回环模式或睡眠模式,但不能同时处于回环模式。如果两者都设置,则LBACK模式优先 3:BRPR波特率预分频器 4:BTR位时间寄存器 具体如何计算波特率可与参考前面文章“13CAN总线通信实验” 5:ECR错误计数器寄存器 6:ESR错误状态寄存器 7:SR状态寄存器 8:ISR中断状态寄存器 9:IER中断使能寄存器 10:ICR中断清除寄存器 11:IDR标识字位描述寄存器 12:DLC字位描述寄存器 13:数据字1和数据字2位描述寄存器 14:AFR验收滤波器寄存器 15:AFR验收滤波器掩码寄存器 16:AFIR接收过滤ID寄存器 4 硬件电路分析 MZ7035系列没有自带的CAN通信接口因此需要使用“米联客”FEP-COMCARD01通信卡 为兼容1.8V和3.3V IO接口使用了TXS0108电平转换芯片 通过跳线帽可以调整VCCADJ的电压: ZYNQ的CAN_TX 和CAN_RX 分别接入到CAN芯片SN65HVD232 5 搭建SOC系统工程 详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。 5.1 SOC系统工程 1:中断设置 2:设置GP Master接口 3:设置复位输出 4:设置PL时钟 5:CAN IP设置 5.2设置AXI外设地址分配 只要添加的AXI总线外设都要正确分配地址,这一步不能遗漏 5.3 编译并导出平台文件 以下步骤简写,有不清楚的看Linux基础篇第四章。 1:打开soc_prj内工程。 2:生成Bit文件。 3:导出到硬件: FileàExpor HardwareàInclude bitstream 4:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。 5:打开vitis,并添加设备树模板。 6:创建工程文件,选择device tree创建,创建完成后编译工程。 7:获得设备树以及启动文件,打开虚拟机将文件拷贝到开发包的指定位置。 5.4 设备树修改及系统编译
1:设备树修改 对应的设备树文件在对应demo的soc_dts文件夹内,新手请直接使用而非自行修改。若要自行修改,则修改主要体现在增加pl节点上。 添加pl串口节点,即pl.dtsi里的内容: 2:编译烧录系统 拷贝上一步写好的设备树到指定路径,拷贝其他文件后,编译uboot,编译kernel,制作镜像并烧录系统。具体步骤参考Linux基础篇第四章。 3:编译驱动 将对应的demo拷贝至/home/uisrc下,同时确保/home/uisrc下有软件开发包uisrc-lab-xlnx。 进入demo的路径,使用make编译驱动。 其中无后缀的文件为我们编译出来的软件。 4:拷贝程序 将编译好的文件拷贝至sd卡上: 6 程序分析 本文的程序从“17 CAN实验”修改而来。 6.1 can_receive.c工程 - #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <net/if.h>
- #include <sys/ioctl.h>
- #include <sys/socket.h>
- #include <linux/can.h>
- #include <linux/can/raw.h>
- int main()
- {
- int s, nbytes;
- struct sockaddr_can addr;
- struct ifreq ifr;
- struct can_frame frame;
- struct can_filter rfilter[1];
- s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建套接字
- strcpy(ifr.ifr_name, "can2");
- ioctl(s, SIOCGIFINDEX, &ifr); //指定 can2 设备
- addr.can_family = AF_CAN;
- addr.can_ifindex = ifr.ifr_ifindex;
- bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定
- //定义接收规则,只接收表示符等于 0x11 的报文
- rfilter[0].can_id = 0x11;
- rfilter[0].can_mask = CAN_SFF_MASK;
- //设置过滤规则
- setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
- while (1)
- {
- nbytes = read(s, &frame, sizeof(frame)); //接收报文 //显示报文
- if (nbytes > 0)
- {
- printf("---------------\n");
- printf("ID=0x%X DLC=%d\n", frame.can_id, frame.can_dlc);
- for (int i = 0; i < frame.can_dlc; i++)
- {
- printf("data[%d]=0x%X\n", i, frame.data[i]);
- }
- }
- sleep(1);
- }
- close(s);
- return 0;
- }
复制代码行18,由于can是块设备,使用SOCK_RAW的模式创建socket。 行20,设定can2为程序收发的外设,如何确定是can2请看下一节的演示结果。 行23,绑定socket与设备。 行28,设定过滤规则,只收取固定的帧ID。 行31,读取报文。 行32~42,循环打印出接收到的报文。 6.2 can_send.c工程 - #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <net/if.h>
- #include <sys/ioctl.h>
- #include <sys/socket.h>
- #include <linux/can.h>
- #include <linux/can/raw.h>
- int main()
- {
- int s, nbytes;
- struct sockaddr_can addr;
- struct ifreq ifr;
- struct can_frame frame[2] = {{0}};
- s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建套接字
- strcpy(ifr.ifr_name, "can2");
- ioctl(s, SIOCGIFINDEX, &ifr); //指定 can2 设备
- addr.can_family = AF_CAN;
- addr.can_ifindex = ifr.ifr_ifindex;
- bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定
- //禁用过滤规则,本进程不接收报文,只负责发送
- setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
- //生成两个报文
- frame[0].can_id = 0x11;
- frame[0].can_dlc = 1;
- frame[0].data[0] = 'Y';
- frame[1].can_id = 0x22;
- frame[1].can_dlc = 1;
- frame[1].data[0] = 'N'; //循环发送两个报文
- while (1)
- {
- nbytes = write(s, &frame[0], sizeof(frame[0])); //发送 frame[0]
- if (nbytes != sizeof(frame[0]))
- {
- printf("Send Error frame[0]\n!");
- break; //发送错误,退出
- }
- sleep(1);
- nbytes = write(s, &frame[1], sizeof(frame[1])); //发送 frame[1]
- if (nbytes != sizeof(frame[1]))
- {
- printf("Send Error frame[1]\n!");
- break;
- }
- sleep(1);
- }
- close(s);
- return 0;
- }
复制代码软件的开头部分和接收部分类似。 行17,创建socket。 行22,绑定socket。 行24,禁用过滤规则。 行26~31,设置两个报文。 行32~48,循环发送两个报文。 6.3 socket类型 socket有三种类型,其中两种十分常见。SOCK_STREAM与SOCK_DGRAM分别为TCP和UDP对应的socket类型,除此之外还有一种类型为SOCK_RAW。 原始套接字是我们需要关心的,因为我们的Socket CAN采用的即是原始套接字。该接口允许对较底层协议进行操作,如IP、ICMP等。原始套接字常用于检验新的协议实现或访问现有服务中配置的新设备。 套接字的工作流程如下: 先启动服务器,通过调用socket()函数建立一个套接字,然后调用bind()函数将该套接字和本地网络地址联系在一起,客户端和服务器之间就可以通过调用read()函数和write()函数来进行发收数据。最后,待数据传送结束后,双方调用close()函数关闭套接字。. 7 演示结果 SD2.0 启动 11 而模式开关为 OFF OFF 将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。 接入12V直流电源开机,登录板卡。 在使用之前,我们需要先对CAN口进行配置,使之正常工作。教程使用的底板有两个CAN口,不同底板的CAN口数量不同,本章会采用通用的测试方法避免差异,但可能略有不同。 众所周知CAN口在Linux系统中和网口一并被归结为了网络设备,原因可能是它们同属于块设备。启动系统后,先输入su命令来切换到root模式: 使用ifconfig -a来查看所有设备: 上图红框可见pl端CAN口已经被识别到并成功初始化了。 使用ip link set can2 type can bitrate 500000设置CAN口的波特率,注意要切换到root用户: 使用ip -d -s link show can0来显示CAN0的详细信息: 检查波特率是否设置成功,不一定是分毫不差,差不多大小就是对的。 使用ip link set can0 up使能CAN0,然后通过ifconfig查看配置: 安装好CAN分析仪配套的软件,我们这里使用的软件为USB_CAN TOOL,以下仅以此软件进行操作演示。将CAN分析仪与电脑连接。打开USB-CAN Tool,选择500kbps的波特率连接CAN分析仪。 回到串口的终端,cd至对应demo的位置: 使用sudo ./can_receive,开始接收从pc发来的消息,使用pc的CAN软件发送消息,配置如下: 终端接收到的消息如下: 可以看到与发送的信息一致。 接下来测试CAN发送,在串口终端输入sudo ./can_send,使用ctrl+c可以终止程序。 这时再来看CAN软件,可以看到正在循环接收0x59和0x4E两个数据。 |