手把手教你用FPGA+SJA1000搞定PCIe转CAN卡(附Vivado 2017.4避坑指南)

张开发
2026/4/21 18:24:27 15 分钟阅读

分享文章

手把手教你用FPGA+SJA1000搞定PCIe转CAN卡(附Vivado 2017.4避坑指南)
FPGA与SJA1000实现PCIe转CAN卡的实战指南在嵌入式系统开发中现场可编程门阵列(FPGA)因其高度灵活性和并行处理能力成为连接不同接口协议的理想选择。本文将详细介绍如何利用Xilinx Artix-7系列FPGA和经典的SJA1000控制器构建一个可靠的PCIe转CAN接口卡。不同于市面上现成的解决方案这种自主设计方案不仅成本可控还能根据具体需求进行深度定制。1. 硬件选型与架构设计选择适合的硬件组件是项目成功的第一步。XC7A200T作为Xilinx Artix-7系列中的中端型号提供了足够的逻辑资源和高速收发器特别适合需要PCIe接口的应用场景。这款FPGA内置了PCIe硬核支持Gen2 x1配置理论带宽达到5Gbps完全满足大多数CAN总线应用的数据传输需求。SJA1000作为一款独立的CAN控制器具有以下优势支持CAN 2.0A和2.0B协议最高1Mbps的通信速率丰富的错误检测和处理机制成熟的Linux驱动支持硬件连接架构如下表所示组件接口类型连接方式备注FPGAPCIe直接连接主机使用GTP Bank 216FPGASJA1000并行总线(8位)需配置片选和中断SJA1000CAN总线通过TJA1050收发器注意终端电阻匹配提示在PCB布局时务必为所有高速差分信号(PCIe和CAN)预留交流耦合电容的位置典型值为0.1μF。2. Vivado工程配置要点使用Vivado 2017.4创建项目时需要特别注意工具版本的兼容性问题。这个版本对Artix-7系列的支持较为成熟但也有一些已知的bug需要规避。2.1 PCIe IP核配置在IP Integrator中添加PCIe核时关键参数设置如下create_ip -name pcie_7x -vendor xilinx.com -library ip -version 3.3 \ -module_name pcie_7x_0 set_property -dict [list \ CONFIG.pcie_blk_locn {X0Y0} \ CONFIG.en_gt_selection {true} \ CONFIG.select_quad {GTP_Artix-7} \ CONFIG.pl_link_cap_max_link_speed {5.0_GT/s} \ CONFIG.pl_link_cap_max_link_width {X1} \ CONFIG.axi_data_width {64_bit} \ CONFIG.pipe_sim {true} \ CONFIG.pf0_device_id {7024} \ CONFIG.pf0_class_code {020000} \ ] [get_ips pcie_7x_0]常见配置错误包括选择了错误的GTP Bank必须使用Bank 216未正确设置参考时钟频率通常为100MHz忽略了AXI接口位宽与后续设计的匹配2.2 时钟与复位设计稳定的时钟和可靠的复位电路是系统正常工作的基础。建议采用以下设计主时钟使用PCIe参考时钟作为主时钟源辅助时钟为SJA1000提供独立的16MHz时钟复位电路上电复位(POR)电路FPGA逻辑产生的软复位PCIe链路训练完成后的自动复位// 复位逻辑示例 always (posedge clk_100m or negedge por_n) begin if (!por_n) begin reset_counter 0; global_reset 1b1; end else if (reset_counter ! 8hFF) begin reset_counter reset_counter 1; global_reset 1b1; end else begin global_reset 1b0; end end3. FPGA与SJA1000的接口实现FPGA需要模拟一个简单的并行总线主机来与SJA1000通信。这种设计既保持了灵活性又避免了复杂的协议转换。3.1 寄存器访问时序SJA1000的寄存器访问时序要求如下参数最小值典型值最大值单位tAS(地址建立时间)10--nstAH(地址保持时间)10--nstDS(数据建立时间)10--nstDH(数据保持时间)10--nstRD(读脉冲宽度)50--nstWR(写脉冲宽度)50--nsVerilog实现示例module sja1000_interface ( input wire clk, input wire reset, // 寄存器接口 input wire [7:0] addr, input wire [7:0] data_in, output reg [7:0] data_out, input wire wr_en, input wire rd_en, // SJA1000物理接口 output reg [7:0] sja_ad, output reg sja_ale, output reg sja_cs_n, output reg sja_rd_n, output reg sja_wr_n, input wire [7:0] sja_data_in ); always (posedge clk or posedge reset) begin if (reset) begin sja_cs_n 1b1; sja_rd_n 1b1; sja_wr_n 1b1; sja_ale 1b0; end else begin case (state) IDLE: begin if (wr_en || rd_en) begin sja_ad addr; sja_ale 1b1; state ADDR_LATCH; end end ADDR_LATCH: begin sja_ale 1b0; sja_cs_n 1b0; if (wr_en) begin sja_ad data_in; sja_wr_n 1b0; state WRITE_DATA; end else begin sja_rd_n 1b0; state READ_DATA; end end // 其他状态省略... endcase end end endmodule3.2 中断处理机制SJA1000的中断信号(INT)需要被FPGA捕获并转换为PCIe中断。推荐的设计包括中断状态寄存器中断使能寄存器边沿检测电路中断脉冲宽度控制// 中断处理逻辑 always (posedge clk or posedge reset) begin if (reset) begin int_status 8h00; int_enable 8h00; pcie_int_n 1b1; end else begin // 捕获SJA1000中断 if (sja_int_n 1b0) begin int_status int_status | 8h01; end // 生成PCIe中断 if ((int_status int_enable) ! 0) begin pcie_int_n 1b0; int_counter 100; // 保持约1us end else if (int_counter 0) begin int_counter int_counter - 1; end else begin pcie_int_n 1b1; end end end4. Linux驱动开发要点在Linux系统中PCIe设备驱动需要处理设备枚举、资源分配和CAN子系统集成等多个方面。4.1 PCIe设备探测驱动首先需要正确识别FPGA实现的PCIe设备static struct pci_device_id pcie_can_ids[] { { PCI_DEVICE(0x10ee, 0x7024) }, // Xilinx Vendor ID 自定义Device ID { 0, } }; static struct pci_driver pcie_can_driver { .name pcie_can, .id_table pcie_can_ids, .probe pcie_can_probe, .remove pcie_can_remove, }; static int pcie_can_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret; // 启用PCI设备 ret pci_enable_device(pdev); if (ret) { dev_err(pdev-dev, Failed to enable PCI device\n); return ret; } // 请求内存区域 ret pci_request_regions(pdev, pcie_can); if (ret) { dev_err(pdev-dev, Failed to request regions\n); goto err_disable; } // 映射BAR0 priv-reg_base pci_iomap(pdev, 0, 0); if (!priv-reg_base) { dev_err(pdev-dev, Failed to map BAR0\n); ret -ENOMEM; goto err_release; } // 初始化CAN控制器 ret can_sja1000_init(priv); if (ret) { dev_err(pdev-dev, Failed to init CAN controller\n); goto err_unmap; } return 0; err_unmap: pci_iounmap(pdev, priv-reg_base); err_release: pci_release_regions(pdev); err_disable: pci_disable_device(pdev); return ret; }4.2 CAN子系统集成Linux内核提供了完善的CAN协议栈支持驱动需要实现以下接口CAN控制器注册struct net_device *dev; struct sja1000_priv *priv; dev alloc_candev(sizeof(struct sja1000_priv), 1); if (!dev) return -ENOMEM; priv netdev_priv(dev); priv-read_reg pcie_can_read_reg; priv-write_reg pcie_can_write_reg; priv-can.clock.freq 16000000; // 16MHz时钟 priv-ocr 0x1A; // 正常输出模式 priv-cdr 0x48; // 时钟分频BasicCAN模式 SET_NETDEV_DEV(dev, pdev-dev); ret register_candev(dev); if (ret) { free_candev(dev); return ret; }寄存器访问函数static u8 pcie_can_read_reg(const struct sja1000_priv *priv, int reg) { struct pcie_can_priv *pcie_priv priv-priv; return ioread8(pcie_priv-reg_base reg); } static void pcie_can_write_reg(const struct sja1000_priv *priv, int reg, u8 val) { struct pcie_can_priv *pcie_priv priv-priv; iowrite8(val, pcie_priv-reg_base reg); }5. 调试技巧与常见问题在实际调试过程中以下几个工具和技巧特别有用5.1 必备调试工具硬件工具逻辑分析仪用于观察并行总线时序示波器检查时钟质量和信号完整性CAN总线分析仪如PCAN-USB软件工具lspci -vvv查看PCIe设备配置空间ip -details link show can0查看CAN接口状态candump和cansendCAN报文收发测试5.2 典型问题排查PCIe设备未被识别检查FPGA的PCIe硬核配置验证参考时钟是否稳定使用示波器检查差分信号质量确认PCIe复位序列正确CAN通信失败确认SJA1000的时钟频率设置正确检查总线终端电阻通常为120Ω验证波特率设置与总线其他节点匹配检查错误计数器状态通过SJA1000的ECC寄存器系统稳定性问题确保电源滤波充分检查PCB布局是否满足高速信号要求验证散热设计是否合理# 常用调试命令示例 # 查看PCIe设备信息 lspci -nn -d 10ee:7024 -vvv # 设置CAN接口波特率 ip link set can0 type can bitrate 500000 # 启用CAN接口 ip link set can0 up # 监控CAN总线流量 candump can06. 性能优化建议当系统基本功能实现后可以考虑以下优化措施DMA传输在FPGA中实现DMA引擎减少CPU中断负载批处理在驱动中实现报文缓冲减少上下文切换时钟优化使用FPGA的PLL生成精确时钟提高CAN定时精度电源管理实现PCIe电源状态管理降低空闲功耗性能指标参考指标基本实现优化后测量条件吞吐量2Mbps5MbpsCAN FD模式延迟500μs200μs报文长度8字节CPU占用率15%5%500Hz报文频率在最后的系统集成阶段建议建立一个完整的测试方案包括压力测试长时间高负载运行兼容性测试不同CAN设备互联异常情况测试总线短路、断开等温度测试高低温环境下的稳定性

更多文章