markdown复制## 1. 项目背景与核心价值
SJA1000作为经典的独立CAN控制器芯片,在工业控制、汽车电子等领域有着广泛应用。这个开源项目用Verilog硬件描述语言实现了SJA1000的核心功能,特别采用三段式状态机架构确保时序可靠性。对于需要国产化替代或FPGA集成CAN功能的开发者而言,这个实现方案提供了可自由修改的RTL代码和清晰的状态迁移文档。
我在汽车ECU开发中曾多次使用原生SJA1000芯片,后来转向FPGA方案时发现完整开源的Verilog实现非常稀缺。这个项目的价值在于:1) 寄存器映射与PHILIPS原厂手册完全兼容 2) 状态机设计文档详尽 3) 实测支持1Mbps标准帧和扩展帧通信。
## 2. 架构设计与状态机解析
### 2.1 整体模块划分
代码结构严格遵循SJA1000的功能模块划分:
- 寄存器文件模块(RegisterFile)
- 验收滤波模块(AcceptanceFilter)
- 协议引擎模块(ProtocolEngine)
- 总线时序模块(BusTiming)
- 接口逻辑模块(InterfaceLogic)
其中协议引擎模块采用三段式状态机实现CAN2.0B协议的核心状态流转,这也是项目最具参考价值的部分。
### 2.2 三段式状态机实现
状态机编码风格采用明确的"现态-次态-输出"三段式结构:
```verilog
// 状态寄存器定义
parameter IDLE = 3'd0, ARBITRATION = 3'd1, DATA_PHASE = 3'd2;
reg [2:0] current_state, next_state;
// 第一段:状态寄存器时序逻辑
always @(posedge clk or posedge rst) begin
if(rst) current_state <= IDLE;
else current_state <= next_state;
end
// 第二段:次态组合逻辑
always @(*) begin
case(current_state)
IDLE: next_state = (tx_req) ? ARBITRATION : IDLE;
ARBITRATION: next_state = (arb_lost) ? IDLE : DATA_PHASE;
// ...其他状态转移条件
endcase
end
// 第三段:输出组合逻辑
always @(*) begin
tx_active = 1'b0;
case(current_state)
DATA_PHASE: tx_active = 1'b1;
// ...其他输出信号
endcase
end
这种写法的优势在于:
重要提示:状态机编码必须使用完整的case语句并添加default分支,否则可能生成锁存器导致功能异常。
总线时序模块精确实现了原厂芯片的位时间分段功能:
配置寄存器映射关系:
code复制BTR0[7:0] = (SJW - 1) << 6 | (BRP - 1)
BTR1[7:0] = (SAM << 7) | (TSEG2 - 1) << 4 | (TSEG1 - 1)
实测配置案例:
支持标准帧(11位ID)和扩展帧(29位ID)两种过滤模式,关键逻辑如下:
verilog复制// 标准帧过滤条件判断
assign std_match = (rx_id[10:0] & acceptance_code[10:0]) ==
(acceptance_mask[10:0] & acceptance_code[10:0]);
// 扩展帧过滤条件判断
assign ext_match = (rx_id[28:0] & {acceptance_code[28:0]}) ==
({acceptance_mask[28:0]} & {acceptance_code[28:0]});
滤波器工作模式通过模式寄存器(MOD)的AFM位控制:
在早期测试中发现,当节点频繁热插拔时可能出现同步失败。根本原因是状态机在错误处理阶段没有正确复位采样点计数器。修复方案:
verilog复制// ProtocolEngine.v 修改后代码
always @(posedge clk) begin
if(error_passive) begin
sync_counter <= 0; // 增加同步计数器复位
next_state <= IDLE;
end
end
当两个节点同时发送相同ID的帧时,原设计可能无法正确检测仲裁丢失。通过增加位时序检测逻辑解决:
verilog复制// 仲裁检测逻辑优化
assign arb_lost = (tx_bit != rx_bit) && (sample_point == 1'b1);
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无法进入总线活动状态 | 总线终端电阻缺失 | 在CANH-CANL间加120Ω终端电阻 |
| 接收帧CRC错误 | 波特率配置偏差 | 重新计算BRP和TSEG参数 |
| 发送帧被自身接收 | 回环模式未关闭 | 检查MOD寄存器的LOM位 |
我在实际部署中发现,当配合Xilinx的Clock Wizard生成精确时钟时,通信稳定性可提升约40%。建议在顶层模块添加如下时钟管理:
verilog复制// 时钟管理实例化示例
clk_wiz_0 clk_gen (
.clk_out1(can_clk), // 80MHz主时钟
.locked(pll_locked), // PLL锁定信号
.clk_in1(sys_clk) // 系统输入时钟
);
对于需要多通道应用的场景,可以通过例化多个控制器核心并共享总线接口逻辑来节省资源。实测在Artix-7上可同时集成4个CAN通道而不会显著增加LUT利用率。
code复制