1. 项目概述:FPGA驱动SJA1000T实现CAN通信
在工业控制和汽车电子领域,CAN总线因其出色的抗干扰能力和实时性成为首选通信方案。最近我完成了一个基于FPGA的CAN通信驱动项目,使用Xilinx Artix-7系列FPGA驱动SJA1000T控制器芯片,成功实现了标准帧和扩展帧的双模式通信。这个项目最特别之处在于,我们不是简单地移植现成的SJA1000T驱动代码,而是从底层重新设计了FPGA与芯片的交互逻辑,使其更适合工业现场的高可靠性要求。
这个驱动方案已经在实际硬件平台上稳定运行超过500小时,支持800kbps的通信速率,误码率低于10^-7。相比市面上常见的单片机驱动方案,我们的FPGA实现具有三个显著优势:首先,通过硬件状态机实现的通信时序控制更加精确,时钟抖动小于1ns;其次,模块化的设计使得代码可移植性极强,只需简单修改引脚约束就能适配不同型号的FPGA;最后,我们加入了独特的异常恢复机制,当检测到总线错误时能在5ms内自动复位重建连接。
2. 硬件架构设计
2.1 核心器件选型
在选择核心器件时,我们重点考虑了工业环境的严苛要求。FPGA选用Xilinx的XC7A35T-2FTG256C,这款芯片具有以下特点:
- 逻辑单元数量33,280个,足够实现复杂的通信协议栈
- 内置180个DSP切片,可扩展数字滤波功能
- 工作温度范围-40°C到+100°C,满足工业级应用
- 256引脚FBGA封装,提供充足的IO资源
SJA1000T控制器选用的是NXP的TJA1050T/3版本,相比基础型号有以下改进:
- 支持5V和3.3V双电压工作模式
- 内置总线保护电路,可承受±8kV的静电放电
- 提供独立的错误诊断引脚(ERR)
- 工作温度范围扩展到-40°C到+125°C
2.2 关键电路设计
2.2.1 电平转换电路
由于FPGA工作在3.3V而SJA1000T是5V器件,我们设计了专门的电平转换电路。数据总线(DATA_CAN[7:0])采用TI的SN74LVC8T245双向电平转换器,具有以下特性:
- 传输延迟仅3.7ns
- 支持3.3V到5V的双向转换
- 输出驱动能力达到32mA
- 使能端(EN)由FPGA控制数据传输方向
控制信号(ALE, WR, RD, CS)则使用74LVC1T45单通道转换器,这种设计比使用多通道芯片更灵活,可以根据实际PCB布局灵活调整器件位置,减少信号串扰。
2.2.2 时钟电路
系统采用40MHz的有源晶振作为主时钟源,经过PLL倍频后为FPGA提供200MHz工作时钟。特别需要注意的是,SJA1000T的CLKOUT引脚输出频率需要设置为16MHz(通过CDR寄存器配置),这个时钟信号经过缓冲器后反馈给FPGA,用于同步双方的通信时序。
重要提示:在实际布线时,时钟信号线必须采用等长走线设计,长度差控制在±5mm以内,否则会导致建立保持时间违例。
3. FPGA逻辑设计
3.1 顶层模块架构
整个设计采用分层架构,顶层模块(can_top.v)主要实现以下功能:
- 时钟域交叉处理(200MHz到40MHz的时钟域转换)
- 按键消抖处理(采用两级寄存器+计数器方案)
- 周期性发送触发逻辑
- 底层驱动模块实例化
verilog复制module can_top(
input clk_200m, // 200MHz系统时钟
input rstn, // 低电平复位
input [2:0] key_in, // 按键输入
// SJA1000T接口信号
output CAN_ALE,
output CAN_WR,
output CAN_RD,
output CAN_CS,
output CAN_RST,
inout [7:0] DATA_CAN
);
// 时钟分频:200MHz->40MHz
reg [2:0] clk_div;
always @(posedge clk_200m or negedge rstn) begin
if(!rstn) clk_div <= 3'd0;
else clk_div <= clk_div + 3'd1;
end
wire clk_40m = clk_div[2]; // 200/5=40MHz
// 按键消抖逻辑
reg [15:0] key_cnt[0:2];
reg [2:0] key_stable;
genvar i;
generate
for(i=0; i<3; i=i+1) begin: KEY_DEBOUNCE
always @(posedge clk_40m) begin
if(key_in[i]) key_cnt[i] <= 16'd0;
else if(key_cnt[i] < 16'hFFFF) key_cnt[i] <= key_cnt[i] + 16'd1;
key_stable[i] <= (key_cnt[i] > 16'd1000); // 25ms消抖时间
end
end
endgenerate
// 驱动模块实例化
can_port u_can_port(
.clk_in(clk_40m),
.reset(!rstn),
.CAN_DATA_SEND_EN(send_en),
// 其他信号连接...
);
endmodule
3.2 核心驱动模块设计
3.2.1 寄存器配置序列
SJA1000T的初始化需要配置14个关键寄存器,我们采用状态机实现自动配置流程。以下是几个关键寄存器的配置细节:
-
模式寄存器(MOD - 0x00):
- 复位值: 0x09 (进入复位模式)
- 工作值: 0x08 (正常模式)
- 特别说明:必须先进入复位模式才能修改其他寄存器
-
总线定时寄存器0/1(BTR0/BTR1 - 0x06/0x07):
- 对于40MHz时钟和800kbps波特率:
- BTR0 = 0x00
- BTR1 = 0x16
- 计算公式:
code复制波特率 = fCLK / (2 × (BRP + 1) × (TSEG1 + TSEG2 + 3)) 其中: BRP = BTR0[5:0] TSEG1 = BTR1[3:0] + 1 TSEG2 = BTR1[6:4] + 1
- 对于40MHz时钟和800kbps波特率:
-
输出控制寄存器(OCR - 0x08):
- 配置值: 0x1A
- 含义:
- 正常输出模式
- TX0推挽输出
- TX1上拉输出
- 输出极性正常
3.2.2 状态机设计
通信状态机采用独热码编码,包含5个主要状态:
-
INIT_RESET (00001): 芯片复位状态
- 拉低RST引脚至少1μs
- 等待30000个时钟周期(40MHz下约750μs)确保芯片完全复位
-
INIT (00010): 寄存器配置状态
- 依次写入14个寄存器配置值
- 每个寄存器写入间隔至少10个时钟周期
- 最后将MOD寄存器改为0x08退出复位模式
-
IDLE (00100): 空闲状态
- 持续监测状态寄存器(SR - 0x03)
- SR[0]=1: 接收缓冲区有数据
- SR[2]=1: 发送缓冲区空
-
DATA_READ (01000): 数据接收状态
- 读取接收缓冲区数据(15字节)
- 处理ID偏移问题(标准帧和扩展帧格式不同)
- 置位接收完成标志
-
DATA_SEND (10000): 数据发送状态
- 写入发送缓冲区数据(13字节)
- 发送命令寄存器(CMR)写入0x01
- 置位发送完成标志
状态转移图如下(文字描述):
- 上电或复位后进入INIT_RESET
- 复位完成后自动跳转到INIT
- 寄存器配置完成后进入IDLE
- IDLE状态下根据SR寄存器值跳转到DATA_READ或DATA_SEND
- 收发完成后都返回IDLE状态
4. 通信协议实现
4.1 标准帧与扩展帧处理
SJA1000T支持两种CAN帧格式,我们的驱动需要处理这两种格式的转换:
-
标准帧(11位ID)存储格式:
- ID0: 帧信息(低4位是DLC)
- ID1: ID10~ID3
- ID2: ID2~ID0 + 5位未使用
- 数据域: 最多8字节
-
扩展帧(29位ID)存储格式:
- ID0: 帧信息(低4位是DLC)
- ID1: ID28~ID21
- ID2: ID20~ID13
- ID3: ID12~ID5
- ID4: ID4~ID0 + 3位未使用
- 数据域: 最多8字节
在代码中,我们通过以下方式处理ID转换:
verilog复制// 发送ID处理
wire [28:0] ext_id = {3'b0, 11'h123, 15'h4567}; // 示例扩展帧ID
assign CAN_ID1_tx = ext_id[28:21];
assign CAN_ID2_tx = ext_id[20:13];
assign CAN_ID3_tx = ext_id[12:5];
assign CAN_ID4_tx = {ext_id[4:0], 3'b000}; // 低5位左移3位
// 接收ID处理
wire [28:0] recv_id = {3'b0, CAN_ID1_rx, CAN_ID2_rx, CAN_ID3_rx, CAN_ID4_rx[7:3]};
4.2 数据收发流程
4.2.1 数据发送流程
- 等待发送缓冲区空(SR[2]=1)
- 写入帧信息到ID0寄存器:
- 标准帧:0x40 | DLC (低4位)
- 扩展帧:0x80 | DLC (低4位)
- 写入ID字段到ID1-ID4寄存器
- 写入数据到DATA1-DATA8寄存器
- 向命令寄存器(CMR)写入0x01触发发送
- 等待发送完成中断或轮询状态寄存器
4.2.2 数据接收流程
- 检测接收缓冲区满(SR[0]=1)
- 读取ID0寄存器获取帧类型和数据长度
- 根据帧类型读取ID字段:
- 标准帧:读取ID1-ID2
- 扩展帧:读取ID1-ID4
- 读取DATA1-DATA8寄存器获取数据
- 向命令寄存器(CMR)写入0x04释放接收缓冲区
5. 调试与优化技巧
5.1 ChipScope调试配置
为了实时监控FPGA内部信号,我们配置了ChipScope逻辑分析仪,主要监测以下信号组:
-
状态机监控组:
- state_c[4:0] (状态机当前状态)
- init_finish (初始化完成标志)
- CAN_DATA_SEND_EN (发送使能)
-
数据收发组:
- CAN_ID1_tx[7:0] (发送ID高字节)
- CAN_DATA1_tx[7:0] (发送数据第一个字节)
- recv_data[7:0] (接收到的数据)
-
错误监测组:
- error_flag (错误标志)
- reset_cnt[31:0] (复位计数器)
- CAN_RXERR_rx[7:0] (接收错误计数器)
配置步骤:
- 在Vivado中创建ILA核
- 设置采样深度为1024
- 选择上升沿触发
- 设置触发条件为error_flag上升沿
- 生成bitstream时包含调试核
5.2 常见问题排查
在实际调试中,我们遇到了几个典型问题及解决方案:
-
问题:发送数据后接收端无响应
- 检查步骤:
- 用示波器测量CANH/CANL差分信号
- 确认终端电阻(120Ω)已正确连接
- 检查ACR/AMR寄存器滤波设置
- 解决方案:发现AMR寄存器配置为全0,改为全FF后问题解决
- 检查步骤:
-
问题:通信一段时间后自动复位
- 检查步骤:
- 监控错误计数器(RXERR/TXERR)
- 检查总线负载情况
- 测量电源纹波
- 解决方案:增加电源滤波电容,降低总线负载率
- 检查步骤:
-
问题:扩展帧ID解析错误
- 检查步骤:
- 对比发送和接收的原始ID数据
- 检查ID偏移处理代码
- 解决方案:发现ID4的低3位未清零,修改掩码操作后正常
- 检查步骤:
6. 性能测试结果
我们对驱动进行了全面测试,主要性能指标如下:
-
通信速率测试:
- 800kbps配置下实测速率:798.4kbps (±0.2%)
- 最大持续吞吐量:680kbps (85%利用率)
- 单帧传输时间:标准帧125μs,扩展帧145μs
-
稳定性测试:
- 连续工作500小时无错误
- 总线错误恢复时间:<5ms
- 温度范围测试:-40°C到+85°C正常工作
-
抗干扰测试:
- 通过1kV快速瞬变脉冲群测试
- 通过8kV静电放电测试
- 在30V/m射频场干扰下通信正常
测试环境配置:
- FPGA: XC7A35T-2FTG256C
- CAN控制器: SJA1000T
- 测试设备: CANoe分析仪
- 环境温度: 25°C±2°C
7. 应用扩展建议
基于当前驱动,还可以进一步扩展以下功能:
-
CAN FD支持:
- 升级到支持CAN FD的控制器如TJA1145
- 修改状态机处理可变速率阶段
- 扩展数据长度到64字节
-
多通道支持:
- 使用FPGA的并行处理能力
- 实例化多个驱动模块
- 增加仲裁逻辑管理多通道通信
-
协议栈集成:
- 添加CANopen或J1939协议解析
- 实现PDO/SDO对象字典
- 支持节点保护和心跳机制
-
安全功能增强:
- 添加CRC校验
- 实现MAC认证
- 支持安全启动
这个FPGA驱动的SJA1000T方案已经在我们的工业控制器中量产使用,实践证明其稳定性和可靠性完全满足严苛的工业环境要求。特别是在高温和高电磁干扰环境下,相比传统的单片机方案,FPGA实现的驱动表现出明显的优势。