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驱动支持
硬件连接架构如下表所示:
| 组件 | 接口类型 | 连接方式 | 备注 |
|---|---|---|---|
| FPGA | PCIe | 直接连接主机 | 使用GTP Bank 216 |
| FPGA | SJA1000 | 并行总线(8位) | 需配置片选和中断 |
| SJA1000 | CAN总线 | 通过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 <= 1'b1; end else if (reset_counter != 8'hFF) begin reset_counter <= reset_counter + 1; global_reset <= 1'b1; end else begin global_reset <= 1'b0; end end3. FPGA与SJA1000的接口实现
FPGA需要模拟一个简单的并行总线主机来与SJA1000通信。这种设计既保持了灵活性,又避免了复杂的协议转换。
3.1 寄存器访问时序
SJA1000的寄存器访问时序要求如下:
| 参数 | 最小值 | 典型值 | 最大值 | 单位 |
|---|---|---|---|---|
| tAS(地址建立时间) | 10 | - | - | ns |
| tAH(地址保持时间) | 10 | - | - | ns |
| tDS(数据建立时间) | 10 | - | - | ns |
| tDH(数据保持时间) | 10 | - | - | ns |
| tRD(读脉冲宽度) | 50 | - | - | ns |
| tWR(写脉冲宽度) | 50 | - | - | ns |
Verilog实现示例:
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 <= 1'b1; sja_rd_n <= 1'b1; sja_wr_n <= 1'b1; sja_ale <= 1'b0; end else begin case (state) IDLE: begin if (wr_en || rd_en) begin sja_ad <= addr; sja_ale <= 1'b1; state <= ADDR_LATCH; end end ADDR_LATCH: begin sja_ale <= 1'b0; sja_cs_n <= 1'b0; if (wr_en) begin sja_ad <= data_in; sja_wr_n <= 1'b0; state <= WRITE_DATA; end else begin sja_rd_n <= 1'b0; 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 <= 8'h00; int_enable <= 8'h00; pcie_int_n <= 1'b1; end else begin // 捕获SJA1000中断 if (sja_int_n == 1'b0) begin int_status <= int_status | 8'h01; end // 生成PCIe中断 if ((int_status & int_enable) != 0) begin pcie_int_n <= 1'b0; int_counter <= 100; // 保持约1us end else if (int_counter > 0) begin int_counter <= int_counter - 1; end else begin pcie_int_n <= 1'b1; 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和cansend(CAN报文收发测试)
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电源状态管理,降低空闲功耗
性能指标参考:
| 指标 | 基本实现 | 优化后 | 测量条件 |
|---|---|---|---|
| 吞吐量 | 2Mbps | 5Mbps | CAN FD模式 |
| 延迟 | 500μs | 200μs | 报文长度8字节 |
| CPU占用率 | 15% | 5% | 500Hz报文频率 |
在最后的系统集成阶段,建议建立一个完整的测试方案,包括:
- 压力测试(长时间高负载运行)
- 兼容性测试(不同CAN设备互联)
- 异常情况测试(总线短路、断开等)
- 温度测试(高低温环境下的稳定性)