1. 项目背景与核心价值
在工业控制、汽车电子和航空航天等领域,CAN总线因其高可靠性和实时性成为主流通信协议。而FPGA凭借其并行处理能力和可编程特性,在通信协议转换和接口控制中展现出独特优势。这次我们要聊的是一个实实在在的工程案例——基于FPGA开发SJA1000T芯片驱动,实现标准帧与扩展帧的双模通信。
这个项目的技术难点在于:SJA1000T作为经典的CAN控制器芯片,其寄存器配置复杂,时序要求严格;而FPGA端需要模拟准确的读写时序,同时处理标准帧(11位标识符)和扩展帧(29位标识符)两种格式。最终我们不仅实现了驱动开发,还完成了桥上板的实际调试,这意味着方案已经具备工程应用条件。
2. 硬件架构设计解析
2.1 系统组成框图
整个系统由三个核心部分组成:
- FPGA主控:采用Xilinx Artix-7系列,负责协议转换和逻辑控制
- SJA1000T芯片:作为CAN协议控制器,处理CAN2.0A/B协议
- CAN收发器:PCA82C250T实现电平转换和总线驱动
2.2 关键接口设计
FPGA与SJA1000T通过以下信号线连接:
verilog复制module sja1000_interface(
input clk_50m, // 主时钟
output reg cs_n, // 片选(低有效)
output reg wr_n, // 写使能(低有效)
output reg rd_n, // 读使能(低有效)
output reg ale, // 地址锁存
inout [7:0] ad, // 复用地址/数据总线
input rst_n, // 复位信号
input irq // 中断信号
);
注意:ALE信号的建立/保持时间必须满足芯片手册要求(典型值30ns),否则会导致地址锁存失败。
3. 寄存器配置详解
3.1 工作模式设置
SJA1000T有BasicCAN和PeliCAN两种模式,我们选择更先进的PeliCAN模式:
c复制// 进入复位模式
write_reg(MOD, 0x09); // RM=1, 复位模式
// 设置时钟分频器
write_reg(CDR, 0x88); // 使用PeliCAN模式,关闭时钟输出
// 设置验收滤波
write_reg(ACR0, 0x00); // 验收码
write_reg(AMR0, 0xFF); // 掩码(全接收)
3.2 波特率配置
假设使用16MHz晶振,配置1Mbps波特率:
code复制BTR0 = 0x00; // 同步跳转宽度=1Tq, 预分频=1
BTR1 = 0x14; // TSEG1=5Tq, TSEG2=2Tq
波特率计算公式:
code复制tq = 2 * (BRP+1) / f_osc
bit_time = (TSEG1+1) + (TSEG2+1) + 1
baudrate = 1 / (bit_time * tq)
4. 帧格式处理实现
4.1 标准帧发送流程
标准帧(11位ID)的发送缓冲区结构:
c复制typedef struct {
uint8_t id_high; // ID10~ID3
uint8_t id_low; // ID2~ID0 | 保留位
uint8_t dlc; // 数据长度
uint8_t data[8]; // 数据域
} StdFrame;
发送函数实现要点:
verilog复制task send_std_frame;
input [10:0] id;
input [3:0] dlc;
input [63:0] data;
begin
wait_tx_ready(); // 等待发送缓冲区空
// 写入帧信息
write_reg(TX_BUF1, id[10:3]);
write_reg(TX_BUF2, {id[2:0], 1'b0});
write_reg(TX_BUF3, {4'h0, dlc});
// 写入数据
for (int i=0; i<dlc; i++)
write_reg(TX_BUF4+i, data[i*8 +:8]);
// 请求发送
write_reg(CMR, 0x01);
end
endtask
4.2 扩展帧接收处理
扩展帧(29位ID)的接收处理:
c复制void handle_ext_frame() {
uint32_t id = (read_reg(RX_BUF1) << 21) |
(read_reg(RX_BUF2) << 13) |
(read_reg(RX_BUF3) << 5) |
(read_reg(RX_BUF4) >> 3);
uint8_t dlc = read_reg(RX_BUF5) & 0x0F;
uint8_t data[8];
for(int i=0; i<dlc; i++) {
data[i] = read_reg(RX_BUF6+i);
}
}
5. 调试问题实录
5.1 典型问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无法进入复位模式 | MOD寄存器写入失败 | 检查ALE时序,确保地址建立时间>30ns |
| 接收不到报文 | 验收滤波设置过严 | 设置AMR=0xFF接收所有报文 |
| 发送失败 | 波特率不匹配 | 用示波器测量实际波特率,调整BTR |
| 数据错误 | 总线终端电阻缺失 | 在CANH/CANL间加120Ω终端电阻 |
5.2 示波器调试技巧
- 时序测量:用双通道分别抓取WR#和DATA信号,确保数据在WR#上升沿前稳定
- 总线波形:观察CANH-CANL差分信号,正常应为对称方波
- 眼图分析:在1Mbps速率下,单个位宽应为1μs±5%
6. FPGA优化实践
6.1 状态机设计
采用三段式状态机控制访问流程:
verilog复制typedef enum {
IDLE,
ADDR_SETUP,
DATA_READ,
DATA_WRITE
} sja_state_t;
always @(posedge clk) begin
case(state)
ADDR_SETUP: begin
ale <= 1;
ad <= addr;
#30 ale <= 0; // 保持30ns
state <= DATA_WRITE;
end
DATA_WRITE: begin
wr_n <= 0;
ad <= wdata;
#40 wr_n <= 1; // 写脉冲宽度
state <= IDLE;
end
endcase
end
6.2 时钟域处理
由于SJA1000T接口是异步的,需要做跨时钟域同步:
verilog复制// 中断信号同步化
reg [2:0] irq_sync;
always @(posedge clk) begin
irq_sync <= {irq_sync[1:0], irq};
end
wire irq_risedge = (irq_sync[2:1] == 2'b01);
7. 性能测试数据
经实际测试,系统达到以下指标:
- 传输速率:稳定支持1Mbps通信
- 帧处理延迟:从接收到FPGA中断到读取完成<50μs
- 错误检测:能正确识别CRC错误、格式错误
- 负载测试:连续发送1000帧无丢包
测试时建议使用CAN分析仪(如PCAN-USB)配合发送压力测试:
python复制import can
bus = can.interface.Bus(channel='PCAN_USBBUS1', bustype='pcan')
for i in range(1000):
msg = can.Message(
arbitration_id=0x123,
data=[i&0xFF for _ in range(8)],
is_extended_id=False
)
bus.send(msg)
8. 工程经验总结
经过这个项目,有几个关键经验值得分享:
-
时序约束必须严谨:在FPGA中要对所有接口信号添加set_input_delay约束,否则高速运行时会出现偶发故障。我们曾遇到在实验室测试正常,但现场出现数据错位的问题,最终发现是时钟偏移未约束导致。
-
双电源处理:SJA1000T的VDD和VDDIO需要分别供电,如果共用电源会导致通信不稳定。建议在PCB布局时这两个电源引脚附近都放置0.1μF去耦电容。
-
温度测试不可少:在-40℃~85℃温度范围内测试发现,低温下晶振起振时间会延长,需要在FPGA中增加500ms的上电延迟逻辑。
-
ID规划建议:实际项目中要提前规划好CAN ID分配方案,特别是扩展帧的29位ID空间。建议将ID划分为:8位设备类型+8位设备地址+13位功能码的层次结构。