基于FPGA的CAN通信,FPGA驱动SJA1000T芯片代码,实现标准帧与扩展帧的通信驱动,已上板调通 品牌型号 CAN SJA1000T 与世面上的不同,代码不是SJA1000T芯片代码,而是驱动该芯片的代码。

一、系统概述:从硬件适配到通信实现

在工业控制、汽车电子等领域,CAN总线以其高可靠性、实时性成为核心通信技术。本文解析的代码套件,以FPGA为控制核心,驱动SJA1000T CAN控制器芯片,实现标准帧(SFF)与扩展帧(EFF)的全功能通信。该系统采用Verilog HDL语言开发,适配40MHz时钟频率,涵盖芯片初始化、数据收发、异常恢复、仿真验证全流程,具备高稳定性、可扩展性及工业级适配能力。

代码套件不仅实现了SJA1000T的底层驱动,更通过模块化设计简化了FPGA与CAN芯片的交互逻辑,同时提供增强型功能(如可变数据长度收发)与调试工具(ChipScope集成),可直接作为CAN通信项目的核心开发模板。

二、系统架构:模块化设计与模块间交互

(一)核心文件清单与功能定位

代码套件包含15个文件,按功能可分为驱动核心、顶层控制、仿真测试、调试配置、辅助定义五大类,具体如下表所示:

文件类别 文件名 核心功能 技术亮点
驱动核心 can_port.v 基础版SJA1000T驱动:寄存器配置、状态机管理、固定长度数据收发、异常检测 采用独热码状态机,确保通信时序稳定

| | canportnew.v | 增强版驱动:在基础版上扩展可变数据长度收发、临时数据缓存 | 支持0-8字节动态数据长度,适配复杂场景 |

| | rs_port.v | FPGA与SJA1000T硬件接口驱动:实现ALE/WR/RD/CS时序控制、双向数据总线管理 | 严格遵循SJA1000T时序手册,支持读写并行处理 |

| 顶层控制 | can_top.v | 系统封装:实例化驱动模块,提供按键控制、周期发送、外部接口适配 | 简化硬件集成,支持一键启动周期性发送 |

基于FPGA的CAN通信,FPGA驱动SJA1000T芯片代码,实现标准帧与扩展帧的通信驱动,已上板调通 品牌型号 CAN SJA1000T 与世面上的不同,代码不是SJA1000T芯片代码,而是驱动该芯片的代码。

| 仿真测试 | cantoptb.v | 仿真激励生成:时钟、复位、按键信号模拟,验证核心模块功能 | 支持自动化仿真验证,可直接用于功能调试 |

| 调试配置 | iconpro.vhd/ilapro_0.vhd等 | ChipScope调试核集成:实时观测FPGA内部信号(如状态机、数据缓冲区) | 支持硬件调试,快速定位通信异常 |

| 辅助定义 | SJAPeliCAN.H/config.h/main.c | SJA1000T寄存器地址、位掩码、MCU初始化参数定义(适配C语言开发场景) | 提供跨语言参考,便于底层寄存器配置对照 |

(二)模块交互关系

系统采用分层设计,各模块职责明确且交互清晰,具体流程如下:

  1. 顶层控制层cantop.v接收外部时钟、复位及按键信号,生成发送使能(CANDATASENDEN),传递至驱动核心层;
  2. 驱动核心层canport.v/canportnew.v根据发送使能,通过状态机调度实现SJA1000T寄存器配置与数据处理,同时调用rsport.v完成硬件时序交互;
  3. 硬件接口层rsport.v将驱动核心的控制指令转换为SJA1000T所需的ALE/WR/RD/CS时序,驱动DATACAN双向总线完成数据读写;
  4. 调试与仿真层:ChipScope配置文件用于硬件调试,cantoptb.v用于软件仿真,共同确保系统功能正确性。

三、核心模块深度解析:从时序到逻辑

(一)rs_port.v:硬件接口时序的“桥梁”

rs_port.v是FPGA与SJA1000T的直接交互模块,严格遵循SJA1000T的读写时序规范,实现双向数据总线的精准控制。

1. 关键信号定义
信号名称 方向 位宽 功能说明
clk 输入 1 40MHz系统时钟
reset 输入 1 全局复位(高有效)
act 输入 1 读写操作使能(高有效)
write0 输入 1 读写控制:1=写操作,0=读操作
need_addr 输入 8 目标寄存器地址
send_data 输入 8 待发送数据(写操作时有效)
recv_data 输出 8 接收数据(读操作时有效)
recv_done 输出 1 读操作完成标志(高脉冲)
send_done 输出 1 写操作完成标志(高脉冲)
DATA_CAN 双向 8 FPGA与SJA1000T的双向数据总线
en 输出 1 DATA_CAN方向控制:1=输出(写),0=输入(读)
2. 核心时序实现

以40MHz时钟(周期25ns)为例,读写时序分为9个时钟周期,具体流程如下:

(1)写操作时序(`write0=1`)
  • 周期1act=1触发操作,en=1DATACAN输出),ALE=1,将needaddr(寄存器地址)写入DATA_CAN
  • 周期2ALE=0CS=0(芯片选中),WR=1(准备写数据);
  • 周期3-5WR=0(写使能),将senddata写入DATACAN,完成数据传输;
  • 周期6WR=1(写结束),CS=1(取消芯片选中);
  • 周期7send_done=1(写完成标志);
  • 周期8-9send_done=0act=0,操作复位。
(2)读操作时序(`write0=0`)
  • 周期1act=1触发操作,en=1ALE=1,将needaddr写入DATACAN
  • 周期2ALE=0CS=0RD=1(准备读数据);
  • 周期3-5RD=0(读使能),en=0DATA_CAN输入),读取SJA1000T输出数据;
  • 周期6recvdata=DATACAN(锁存接收数据),RD=1(读结束);
  • 周期7recv_done=1(读完成标志);
  • 周期8-9recv_done=0act=0,操作复位。
3. 时序设计亮点
  • 双向总线控制:通过en信号精准控制DATA_CAN的输入/输出方向,避免总线冲突;
  • 操作同步:以act信号为触发,确保读写操作与系统时钟严格同步,降低时序偏差风险;
  • 完成标志senddone/recvdone高脉冲信号,为上层模块提供明确的操作结束反馈。

(二)can_port.v:CAN通信的“大脑”

can_port.v是驱动核心的基础版,实现SJA1000T的初始化、数据收发逻辑及异常处理,核心是状态机调度寄存器配置

1. 核心功能模块划分
(1)SJA1000T寄存器配置

系统上电后需初始化SJA1000T的14个关键寄存器,配置参数及功能如下表所示:

寄存器名称 地址 配置值(复位后) 功能说明
模式控制寄存器(MOD) 0x00 0x09(复位模式)→ 0x08(正常模式) 0x09:进入复位模式以配置寄存器;0x08:切换至正常通信模式
定时器0(TIMER0) 0x06 0x00 波特率配置:配合TIMER1实现800kbps(Fosc=16MHz)
定时器1(TIMER1) 0x07 0x16 波特率配置:Tseg1=7,Tseg2=2,同步段1,总位宽10
输出控制寄存器(OCR) 0x08 0x1A 正常输出模式(0x02),TX0下拉(0x08),TX1上拉(0x80),输出极性控制(0x20)
时钟分频寄存器(CDR) 0x1F 0xC8 0x80:PeliCAN模式;0x08:关闭时钟输出;避免时钟干扰
验收代码寄存器(ACR0~ACR3) 0x10~0x13 0x04、0xF0、0xFF、0xFF 控制ID滤波:仅接收符合代码的CAN帧
验收屏蔽寄存器(AMR0~AMR3) 0x14~0x17 0xFF、0xFF、0xFF、0xFF 全1表示不屏蔽任何ID位,接收所有符合ACR的帧
(2)状态机设计:5状态独热码调度

采用独热码编码(5位)设计状态机,避免状态跳转竞争,确保通信流程稳定,各状态功能如下:

状态名称 编码 触发条件 核心操作 下一状态
INIT_RESET 00001 系统复位/重配置 等待SJA1000T上电就绪(30000个时钟周期),拉低CAN_RST完成芯片复位 INIT
INIT 00010 INIT_RESET完成 按顺序写入14个寄存器配置值,置位init_finish IDLE
IDLE 00100 INIT完成/收发操作结束 读取SJA1000T状态寄存器(SR):
- SR[0]=1:接收缓冲区满→触发DATAREAD
- SR[2]=1且发送使能→触发DATA
SEND
DATAREAD/DATASEND/保持
DATA_READ 01000 IDLE触发接收 读取接收缓冲区数据(ID+8字节数据),存储至CANIDrx/CANDATArx,接收完成后清除缓冲区 IDLE
DATA_SEND 10000 IDLE触发发送 将预定义数据(CANIDtx/CANDATAtx)写入发送缓冲区,通过命令寄存器(CMR)触发发送 IDLE
(3)ID处理机制:解决SJA1000T位偏移问题

SJA1000T在PeliCAN模式下采用29位扩展帧ID(ID28~ID0),但数据存储存在3位偏移,需通过软件处理实现正确ID识别:

  • 发送端:将32位测试ID(IDtest=0xC080C00)左移3位,拆分至CANID1tx~CANID4_tx
  • CANID1tx = ID_test[28:21](高8位)
  • CANID2tx = ID_test[20:13](中8位)
  • CANID3tx = ID_test[12:5](低8位高7位)
  • CANID4tx = {ID_test[4:0], 3'b000}(低5位左移3位)
  • 接收端:实际有效ID需右移3位恢复,公式为:
    verilog
    realrecvid = {3'b000, CANID1rx, CANID2rx, CANID3rx, CANID4rx[7:3]};
(4)异常处理:保障系统鲁棒性

系统支持手动重配置与自动故障恢复,触发条件及处理流程如下:

异常类型 触发条件 处理流程
5秒无数据 reset_cnt>=200000000(40MHz时钟下约5秒) 置位needreset,状态机跳转至INITRESET,重新初始化芯片
总线错误 SJA1000T状态寄存器SR[7]=1(总线离线) 同上,强制复位恢复总线连接
错误计数超限 接收错误计数器(RXERR)>=90或发送错误计数器(TXERR)>=90 同上,避免错误累积导致通信中断
手动重配置 re_config信号高有效 立即触发INIT_RESET,适用于手动调试场景
2. 数据收发流程详解
(1)数据接收流程(DATA_READ状态)
  1. 状态跳转:IDLE状态检测到SR[0]=1(接收缓冲区满),跳转至DATA_READ;
  2. 地址配置:readaddrpath指向接收缓冲区地址(rxtxaddr[0]~rxtxaddr[14]);
  3. 数据读取:通过rs_port.v读取15个字节数据(ID0~ID4+8字节数据+错误计数器);
  4. 数据存储:按顺序存储至CANID0rx~CANID4rxCANDATA1rx~CANDATA8rx
  5. 接收完成:置位CANDATARECV_DONE(高脉冲),清除接收缓冲区,跳转回IDLE。
(2)数据发送流程(DATA_SEND状态)
  1. 状态跳转:IDLE状态检测到SR[2]=1(发送缓冲区空)且CANDATASENDEN高,跳转至DATASEND;
  2. 数据准备:将预定义的CANIDtx/CANDATAtx写入tx_data数组;
  3. 数据写入:通过rs_port.v将13个字节数据(ID0~ID4+8字节数据)写入发送缓冲区;
  4. 触发发送:向SJA1000T命令寄存器(CMR)写入0x01(发送请求);
  5. 发送完成:置位CANDATASEND_DONE(高脉冲),跳转回IDLE。

(三)can_port_new.v:增强型驱动的“升级点”

canportnew.v在基础版can_port.v上新增可变数据长度收发功能,解决固定8字节数据的局限性,核心升级点如下:

1. 新增信号与逻辑
  • 数据长度计数器
  • txDLC(4位):发送数据长度,由CANID0_tx的低4位(DLC3~DLC0)解析得到,范围0-8;
  • rxDLC(4位):接收数据长度,由CANID0_rx的低4位解析得到,范围0-8;
  • 临时缓存寄存器tmpCANIDrx/tmpCANDATArx,用于锁存接收数据,避免传输过程中数据被覆盖。
2. 收发逻辑优化
(1)接收逻辑:动态适配数据长度

在DATAREAD状态下,通过rxDLC控制接收字节数,核心代码片段如下:

if (read_cnt < (rx_DLC + 6)) begin  // 6=ID0~ID4(5字节)+1字节偏移
    case(rx_DLC)
        4'd0: begin  // 0字节数据:仅接收ID
            if(read_cnt==4'd5) begin CAN_ID4_rx <= recv_data_path; DATA_RECEIVE_DO <=1'b1; end
        end
        4'd1: begin  // 1字节数据:ID+1字节数据
            if(read_cnt==4'd6) begin CAN_DATA1_rx <= recv_data_path; DATA_RECEIVE_DO <=1'b1; end
        end
        // ... 其他长度(2-8字节)类似,按rx_DLC动态调整接收字节数
        default: read_state <=3'b001;
    endcase
end
(2)发送逻辑:动态控制发送长度

在DATASEND状态下,通过txDLC控制发送字节数,核心代码片段如下:

if (send_cnt < (tx_DLC + 5)) begin  // 5=ID0~ID4(5字节)
    send_addr_path <= rx_tx_addr[send_cnt];
    send_data_path <= tx_data[send_cnt];
end else begin  // 发送完成,触发发送请求
    send_addr_path <=8'h01;  // 命令寄存器地址
    send_data_path <=8'h01;  // 发送请求命令
end
3. 增强功能价值
  • 灵活性提升:支持0-8字节任意长度数据收发,适配不同应用场景(如短指令帧、长数据帧);
  • 数据安全性:临时缓存寄存器避免数据覆盖,确保接收数据的完整性;
  • 兼容性保留:完全兼容基础版接口,可直接替换can_port.v使用。

(四)can_top.v:系统集成的“入口”

can_top.v作为顶层模块,封装底层驱动逻辑,提供简洁的外部接口,便于硬件集成与功能调试。

1. 核心功能实现
(1)按键防抖与发送控制

通过两级寄存器(key0r/key02r)实现按键防抖,避免机械抖动导致的误触发,核心代码如下:

always @(posedge clk or negedge rstn) begin
    if(!rstn) begin
        key0_r <=1'b0;
        key0_2r <=1'b0;
    end else begin
        key0_r <= key0;       // 一级缓存
        key0_2r <= key0_r;    // 二级缓存,消除抖动
    end
end
assign start_cnt = (!key0_r) && key0_2r;  // 按键按下边沿检测

按键触发后,计数器cnt1开始累加,当达到CNTMAX(默认99999)时,生成CANDATASENDEN信号,实现周期性发送(40MHz时钟下约2.5ms/帧,可通过修改CNT_MAX调整周期)。

(2)模块实例化与接口封装

实例化canport模块,将底层驱动的控制信号(CANALE/CANWR/CANRD/CANCS/CANRST)与双向数据总线(DATA_CAN)对外暴露,简化硬件连接,核心代码如下:

can_port can_port(
    .clk_in(clk),
    .reset(!rstn),
    .re_config(1'b0),
    .can_auto_reset(1'b0),
    .CAN_DATA_SEND_EN(CAN_DATA_SEND_EN),
    .CAN_ALE(CAN_ALE),
    .CAN_WR(CAN_WR),
    .CAN_RD(CAN_RD),
    .CAN_CS(CAN_CS),
    .CAN_RST(CAN_RST),
    .DATA_CAN(DATA_CAN),
    .en()
    // 其他未使用接口省略
);
2. 顶层设计亮点
  • 接口简化:隐藏底层复杂逻辑,对外仅暴露时钟、复位、按键、CAN芯片接口,降低硬件集成难度;
  • 可配置性:通过修改CNT_MAX参数,可快速调整发送周期,适配不同实时性需求;
  • 兼容性:支持替换为canportnew.v模块,无缝扩展可变数据长度功能。

(五)can_top_tb.v:仿真验证的“工具”

cantoptb.v是系统的仿真测试模块,通过生成激励信号验证核心功能的正确性,为硬件调试提供前期保障。

1. 激励信号生成
  • 时钟信号:生成20ns周期(50MHz)时钟(仿真可调整,硬件为40MHz),核心代码如下:
    verilog
    initial begin
    clk =0;
    forever #(
    p/2) clk = ~clk; // p=20,周期20ns
    end
  • 复位信号:初始拉低rstn(复位有效),10个时钟周期后释放,核心代码如下:
    verilog
    initial begin
    rstn =1'b0;
    #(
    p*10+3) rstn =1'b1; // 203ns后释放复位
    end
    `
  • 按键信号:依次触发key0/key1/key2按键,模拟手动配置与发送控制,核心代码如下:
    verilog
    initial begin
    key0=1'b1; key1=1'b1; key2=1'b1;
    #(
    p20) key0=1'b0; #(p10) key0=1'b1; // key0按下10个周期
    #(
    p100) key1=1'b0; #(p10) key1=1'b1; // key1按下10个周期
    #(
    p100) key2=1'b0; #(p10) key2=1'b1; // key2按下10个周期
    end
2. 仿真验证逻辑

等待init_finish信号置位(SJA1000T初始化完成)后,持续运行仿真,通过观测以下信号验证功能:

  • CANDATASEND_DONE:是否在发送周期到达后产生高脉冲;
  • CANDATARECV_DONE:是否在接收缓冲区满后产生高脉冲;
  • CANIDrx/CANDATArx:接收数据是否与发送数据一致;
  • 状态机信号(statec):是否按预期在INITRESET→INIT→IDLE→DATASEND/DATAREAD间跳转。

四、调试工具与辅助文件:加速开发效率

(一)ChipScope调试核:硬件调试的“眼睛”

代码套件中的iconpro.vhd(集成控制器)与ilapro_0.vhd(逻辑分析仪)是Xilinx ChipScope工具的配置文件,支持实时观测FPGA内部信号:

  • 观测信号选择:可配置观测状态机(statec)、数据缓冲区(CANIDrx/CANDATArx)、错误计数器(CANRXERRrx/CANTXERR_rx)等关键信号;
  • 调试流程:将调试核集成到FPGA工程中,通过JTAG接口连接开发板,在ChipScope软件中捕获信号波形,定位通信异常(如状态机卡死、数据传输错误)。

(二)C语言辅助文件:跨语言参考的“桥梁”

SJAPeliCAN.H/config.h/main.c是基于MCU的SJA1000T驱动代码,虽与FPGA代码语言不同,但提供以下参考价值:

  • 寄存器定义:明确SJA1000T各寄存器的地址、位掩码及功能,可与Verilog代码中的寄存器配置对照,避免配置错误;
  • 初始化流程:C语言代码中的Peli_Init()函数展示了SJA1000T的标准初始化流程(复位→配置寄存器→切换正常模式),与Verilog代码逻辑一致,可作为功能验证参考;
  • 中断处理:C语言代码中的接收中断服务函数(Peli_RXD())展示了中断驱动的接收逻辑,为FPGA代码扩展中断功能提供思路。

五、工程实践:从代码到硬件的落地

(一)硬件适配要点

  1. 电平匹配:SJA1000T为5V器件,FPGA为3.3V器件,DATA_CAN总线及控制信号(ALE/WR/RD/CS/RST)需通过电平转换芯片(如74HC245)适配,避免电平冲突;
  2. 时钟配置:硬件时钟需为40MHz,若使用其他频率,需重新计算TIMER0/TIMER1寄存器值以匹配目标波特率(如500kbps对应TIMER0=0x00TIMER1=0x5C);
  3. 终端电阻:CAN总线两端需焊接120Ω终端电阻,减少信号反射,确保通信稳定性;
  4. IO资源分配:根据FPGA开发板的IO banks分配,合理规划CANALE/CANWR/CANRD/CANCS/CANRSTDATACAN的引脚,避免IO冲突。

(二)软件配置步骤

  1. 参数修改
    - 发送数据:修改canport.v中的IDtest(目标ID)和CANDATA1tx~CANDATA8tx(发送数据);
    - 波特率:根据硬件时钟调整initdata[1](TIMER0)和initdata[2](TIMER1);
    - 发送周期:修改cantop.v中的CNTMAX参数,调整周期性发送间隔。
  2. 功能选择
    - 固定长度收发:使用canport.v模块;
    - 可变长度收发:使用can
    portnew.v模块,通过CANID0_tx的低4位设置发送长度;
  3. 仿真验证
    - 运行cantoptb.v仿真,验证状态机跳转、数据收发及异常处理功能;
    - 观测CANDATASENDDONE/CANDATARECVDONE信号,确保收发流程正常。
  4. 硬件调试
    - 集成ChipScope调试核,下载程序到FPGA开发板;
    - 连接CAN总线分析仪(如USBCAN),观测发送数据是否正确;
    - 若接收不到数据,检查验收滤波寄存器(ACR/AMR)配置或总线连接。

(三)常见问题与解决方案

问题现象 可能原因 解决方案
芯片初始化失败(init_finish不置位) 1. CAN_RST时序错误;2. 寄存器配置值错误;3. 硬件连接错误 1. 检查INITRESET状态的CANRST时序,确保复位时间足够;2. 对照SJA1000T手册,确认寄存器配置值;3. 检查CAN_RST引脚连接是否正确
发送无响应(CANDATASEND_DONE不置位) 1. 发送缓冲区未就绪(SR[2]=0);2. 命令寄存器(CMR)配置错误;3. 总线错误 1. 观测SJA1000T状态寄存器SR[2],确保发送缓冲区空;2. 确认CMR寄存器写入0x01(发送请求);3. 检查CAN总线连接及终端电阻
接收不到数据(CANDATARECV_DONE不置位) 1. 验收滤波配置错误;2. 接收缓冲区未满(SR[0]=0);3. 总线无数据 1. 调整ACR/AMR寄存器,确保接收目标ID;2. 确认CAN总线有数据发送;3. 观测SR[0]信号,确认接收缓冲区状态
频繁触发自动复位 1. 5秒无数据接收;2. 总线错误;3. 错误计数器超限 1. 检查CAN总线是否有数据发送;2. 排查总线连接(如终端电阻、电平转换);3. 调整错误计数器阈值(如将90改为120)

六、扩展方向:功能升级与场景适配

(一)功能扩展

  1. 多ID接收:修改验收代码寄存器(ACR)和验收屏蔽寄存器(AMR),配置多个目标ID,实现多ID过滤与接收;
  2. 中断驱动:当前代码采用轮询方式检测收发状态,可通过配置SJA1000T的中断使能寄存器(IER),启用接收/发送中断,减少FPGA的CPU占用;
  3. 数据缓存:新增FIFO模块,缓存接收数据,避免数据溢出,适用于高数据量场景;
  4. 波特率自适应:通过检测CAN总线的波特率,动态调整TIMER0/TIMER1寄存器值,实现波特率自适应,提升系统兼容性。

(二)场景适配

  1. 工业控制:扩展多节点通信功能,支持CANopen协议,实现多设备间的实时数据交互;
  2. 汽车电子:适配汽车CAN总线的高可靠性要求,增加数据校验(如CRC)、错误重发机制;
  3. 物联网:结合FPGA的低功耗特性,优化代码以降低功耗,适配物联网场景的低功耗需求。

七、总结

本代码套件基于FPGA实现了SJA1000T CAN控制器的全功能驱动,涵盖芯片初始化、数据收发、异常恢复、仿真验证及硬件调试全流程。代码采用模块化设计,逻辑清晰、稳定性高,同时提供增强型功能(可变数据长度收发)与跨语言参考(C语言辅助文件),可直接作为工业控制、汽车电子等领域CAN通信项目的核心开发模板。

通过本文的解析,开发者可深入理解FPGA与SJA1000T的交互逻辑,快速完成代码适配与硬件集成,同时基于扩展方向实现功能升级,满足不同应用场景的需求。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐