FPGA入门:CAN总线原理与Verilog代码详解

目录

[一、CAN 总线核心原理](#一、CAN 总线核心原理)

[1. 物理层特性](#1. 物理层特性)

[2. 协议层核心概念](#2. 协议层核心概念)

(1)位时序

(2)帧结构(标准数据帧)

(3)关键机制

[二、FPGA 实现 CAN 的核心模块](#二、FPGA 实现 CAN 的核心模块)

[三、Verilog 代码实现(以 50MHz 时钟、1Mbps 波特率为例)](#三、Verilog 代码实现(以 50MHz 时钟、1Mbps 波特率为例))

[1. 全局参数定义](#1. 全局参数定义)

[2. 位时序模块(CAN Bit Timing Generator)](#2. 位时序模块(CAN Bit Timing Generator))

[3. CRC 计算模块(CAN CRC Generator)](#3. CRC 计算模块(CAN CRC Generator))

[4. 发送模块(CAN Transmitter)](#4. 发送模块(CAN Transmitter))

[5. 接收模块(CAN Receiver)](#5. 接收模块(CAN Receiver))

[6. 顶层模块(CAN Top Module)](#6. 顶层模块(CAN Top Module))

四、硬件验证

[1. 硬件连接](#1. 硬件连接)

[2. 验证步骤](#2. 验证步骤)

五、关键注意事项与扩展功能

[1. 注意事项](#1. 注意事项)

[2. 扩展功能](#2. 扩展功能)

六、总结


CAN(Controller Area Network,控制器局域网)是一种差分信号、多主从、异步串行通信总线 ,核心特点是高可靠性、实时性、抗干扰能力强 ,广泛应用于汽车电子、工业控制、智能家居等领域。FPGA 实现 CAN 接口的核心是通过硬件逻辑 模拟 CAN 协议的位时序、帧结构、仲裁机制、错误处理 ,再配合 CAN 收发器(如 TJA1050)实现物理层连接。以下从原理、核心模块设计、Verilog 代码实现、硬件验证四方面详细说明:

一、CAN 总线核心原理

1. 物理层特性

  • 总线电平 :采用差分信号传输,分为两种状态:
    • 显性电平(Dominant):CAN_H 比 CAN_L 高约 2V(总线电平为 0,优先级高);
    • 隐性电平(Recessive):CAN_H 与 CAN_L 电压差为 0V(总线电平为 1,优先级低);
  • 总线拓扑:多节点挂在 CAN_H 和 CAN_L 总线上,总线两端需接 120Ω 终端电阻;
  • 收发器:FPGA 需通过 CAN 收发器(如 TJA1050)连接总线,FPGA 输出的逻辑电平(TX)转换为差分信号,总线差分信号转换为逻辑电平(RX)给 FPGA。

2. 协议层核心概念

(1)位时序

CAN 的位时间由多个时间量子(TQ) 组成,标准位时序划分(以 1Mbps 波特率为例):

  • 同步段(SYNC_SEG):1TQ,用于节点同步,检测总线电平跳变;
  • 传播段(PROP_SEG):1~8TQ,补偿总线传输延迟;
  • 相位缓冲段 1(PHASE_SEG1):1~8TQ,用于相位调整;
  • 相位缓冲段 2(PHASE_SEG2):1~8TQ,用于相位调整;
  • 重同步跳转宽度(SJW):1~4TQ,允许节点在同步时调整相位的最大范围;
  • 总位时间 = SYNC_SEG + PROP_SEG + PHASE_SEG1 + PHASE_SEG2(通常为 8~25TQ)。
(2)帧结构(标准数据帧)

CAN 帧分为数据帧、远程帧、错误帧、过载帧,最常用的是标准数据帧,结构如下:

字段 长度(位) 描述
帧起始(SOF) 1 显性电平(0),标志帧开始;
仲裁场 11+1 11 位标准 ID(优先级:ID 越小优先级越高)+ RTR 位(0 = 数据帧,1 = 远程帧);
控制场 6 IDE 位(0 = 标准帧,1 = 扩展帧)+ R0 位(保留)+ 4 位 DLC(数据长度 0~8);
数据场 0~64 数据长度由 DLC 决定(1 字节 = 8 位);
CRC 场 15+1 15 位 CRC 校验码 + 1 位 CRC 界定符(隐性电平);
ACK 场 2 1 位 ACK 位(接收方拉低为应答)+ 1 位 ACK 界定符(隐性电平);
帧结束(EOF) 7 7 位隐性电平,标志帧结束。
(3)关键机制
  • 仲裁机制:多节点同时发送时,通过 ID 竞争总线,ID 越小优先级越高,发送显性电平的节点获胜,其他节点转为接收;
  • 位填充:为保证同步,连续 5 个相同电平后插入 1 个相反电平(接收时需解除填充);
  • 错误处理:节点检测到错误(位错误、填充错误、CRC 错误等)时,发送错误帧(6 个显性电平),通知其他节点;
  • 应答机制:接收方正确接收数据后,在 ACK 位拉低总线(显性)应答。

二、FPGA 实现 CAN 的核心模块

FPGA 实现 CAN 接口需拆解为物理层接口、位时序模块、发送模块、接收模块、错误处理模块、控制模块,再通过顶层模块整合:

  1. 物理层接口:连接 CAN 收发器(TJA1050),处理 TX/RX 信号;
  2. 位时序模块:生成 CAN 位时间的 TQ 时钟,实现位同步和相位调整;
  3. 发送模块:构建 CAN 帧(标准数据帧),实现位填充、CRC 计算、仲裁逻辑;
  4. 接收模块:解析 CAN 帧,实现位同步、位填充解除、CRC 校验、应答检测;
  5. 错误处理模块:检测总线错误,发送错误帧,管理节点错误状态;
  6. 控制模块:协调各模块工作,提供外部配置接口(如波特率、ID、数据长度等)。

三、Verilog 代码实现(以 50MHz 时钟、1Mbps 波特率为例)

1. 全局参数定义

verilog

复制代码
// CAN参数配置
parameter CLK_FREQ    = 50_000_000;  // 系统时钟频率(50MHz)
parameter CAN_BAUD    = 1_000_000;   // CAN目标波特率(1Mbps)
parameter TQ_COUNT    = 10;          // 每位包含的TQ数量(10TQ/位,1Mbps对应1μs/位)
parameter SYNC_SEG    = 1;           // 同步段(1TQ)
parameter PROP_SEG    = 3;           // 传播段(3TQ)
parameter PHASE_SEG1  = 3;           // 相位缓冲段1(3TQ)
parameter PHASE_SEG2  = 3;           // 相位缓冲段2(3TQ)
parameter SJW         = 2;           // 重同步跳转宽度(2TQ)

// CAN帧字段参数
parameter STD_ID_LEN  = 11;          // 标准ID长度(11位)
parameter DLC_LEN     = 4;           // DLC长度(4位)
parameter DATA_MAX_LEN= 8;           // 最大数据长度(8字节)
parameter CRC_LEN     = 15;          // CRC长度(15位)

// 状态定义
typedef enum {IDLE, SYNC, ARBITRATION, CONTROL, DATA, CRC, ACK, EOF} can_state_t;

2. 位时序模块(CAN Bit Timing Generator)

功能:生成 TQ 时钟(位时间的最小单位),实现位同步和相位调整。

verilog

复制代码
module can_bit_timing(
    input  logic clk,          // 系统时钟(50MHz)
    input  logic rst_n,        // 复位信号(低电平有效)
    input  logic can_rx,       // CAN接收信号(来自收发器)
    output logic tq_clk,       // TQ时钟(1TQ=100ns,50MHz/500=100kHz?不对,重新计算:50MHz/(TQ_COUNT*CAN_BAUD) = 50e6/(10*1e6)=5MHz,TQ时钟5MHz,1TQ=200ns,10TQ=2μs?哦,之前参数错了,1Mbps位时间1μs,所以TQ_COUNT=10的话,TQ时钟=50e6/(10*1e6)=5MHz,1TQ=200ns,10TQ=2μs,不对,应该TQ_COUNT=5,50e6/(5*1e6)=10MHz,1TQ=100ns,5TQ=500ns,还是不对,正确计算:TQ时钟频率 = CLK_FREQ / (位时间的TQ数),位时间=1/CAN_BAUD=1μs,所以TQ时钟频率= TQ_COUNT / 位时间 = TQ_COUNT * CAN_BAUD,比如TQ_COUNT=8,TQ时钟=8*1e6=8MHz,50MHz/8MHz=6.25,不是整数,所以选TQ_COUNT=10,CAN_BAUD=500kHz,这样TQ时钟=10*500kHz=5MHz,50MHz/5MHz=10,整数分频,之前波特率调整为500kHz更合理,避免非整数分频误差,这里修正参数:

// 修正参数(50MHz时钟,500kHz波特率,10TQ/位,位时间2μs)
parameter CAN_BAUD    = 500_000;   // CAN目标波特率(500kHz)
parameter TQ_COUNT    = 10;        // 每位包含的TQ数量(10TQ/位,2μs/位)
parameter TQ_CLK_FREQ = CAN_BAUD * TQ_COUNT;  // TQ时钟频率=5MHz
parameter DIVISOR     = CLK_FREQ / TQ_CLK_FREQ;  // 分频系数=50e6/5e6=10

    output logic sync_seg,     // 同步段标志
    output logic prop_seg,     // 传播段标志
    output logic phase_seg1,   // 相位缓冲段1标志
    output logic phase_seg2,   // 相位缓冲段2标志
    output logic bit_start,    // 位开始标志
    output logic bit_end       // 位结束标志
);

logic [3:0] tq_cnt = 4'd0;    // TQ计数器(0~TQ_COUNT-1)
logic [3:0] bit_cnt = 4'd0;   // 位计数器(0~TQ_COUNT-1)
logic sync_detected = 1'b0;   // 同步检测标志

// 生成TQ时钟(5MHz)
always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tq_cnt <= 4'd0;
        tq_clk <= 1'b0;
    end else begin
        if (tq_cnt == DIVISOR/2 - 1) begin  // 50%占空比
            tq_clk <= ~tq_clk;
            tq_cnt <= tq_cnt + 1'b1;
        end else if (tq_cnt == DIVISOR - 1) begin
            tq_clk <= ~tq_clk;
            tq_cnt <= 4'd0;
        end else begin
            tq_cnt <= tq_cnt + 1'b1;
        end
    end
end

// 位时序分段
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_cnt <= 4'd0;
        sync_seg <= 1'b0;
        prop_seg <= 1'b0;
        phase_seg1 <= 1'b0;
        phase_seg2 <= 1'b0;
        bit_start <= 1'b0;
        bit_end <= 1'b0;
        sync_detected <= 1'b0;
    end else begin
        // 同步检测(检测CAN_RX电平跳变)
        if (can_rx != can_rx_prev) begin
            sync_detected <= 1'b1;
            bit_cnt <= 4'd0;  // 同步时重置位计数器
        end

        // 位计数器递增
        if (bit_cnt == TQ_COUNT - 1) begin
            bit_cnt <= 4'd0;
            bit_end <= 1'b1;
        end else begin
            bit_cnt <= bit_cnt + 1'b1;
            bit_end <= 1'b0;
        end

        // 时序分段标志
        sync_seg   <= (bit_cnt < SYNC_SEG) ? 1'b1 : 1'b0;
        prop_seg   <= (bit_cnt >= SYNC_SEG && bit_cnt < SYNC_SEG + PROP_SEG) ? 1'b1 : 1'b0;
        phase_seg1 <= (bit_cnt >= SYNC_SEG + PROP_SEG && bit_cnt < SYNC_SEG + PROP_SEG + PHASE_SEG1) ? 1'b1 : 1'b0;
        phase_seg2 <= (bit_cnt >= SYNC_SEG + PROP_SEG + PHASE_SEG1 && bit_cnt < TQ_COUNT) ? 1'b1 : 1'b0;

        // 位开始标志(同步段开始时)
        bit_start <= (bit_cnt == 4'd0) ? 1'b1 : 1'b0;

        // 同步检测标志清零
        if (sync_seg) begin
            sync_detected <= 1'b0;
        end
    end
end

logic can_rx_prev;
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        can_rx_prev <= 1'b1;  // CAN空闲时为隐性电平(1)
    end else begin
        can_rx_prev <= can_rx;
    end
end

endmodule

3. CRC 计算模块(CAN CRC Generator)

功能:计算 CAN 帧(从 SOF 到数据场)的 15 位 CRC 校验码。

verilog

复制代码
module can_crc(
    input  logic clk,          // 系统时钟(50MHz)
    input  logic rst_n,        // 复位信号(低电平有效)
    input  logic crc_en,       // CRC计算使能
    input  logic data_bit,     // 输入数据位
    output logic [CRC_LEN-1:0] crc_out,  // CRC输出
    output logic crc_done      // CRC计算完成
);

logic [CRC_LEN:0] crc_reg = {CRC_LEN+1{1'b1}};  // CRC初始值为全1
logic [3:0] bit_cnt = 4'd0;
logic total_bits;  // 需计算CRC的总位数(SOF(1) + ID(11) + RTR(1) + IDE(1) + R0(1) + DLC(4) + 数据位(8*DLC))

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        crc_reg <= {CRC_LEN+1{1'b1}};
        bit_cnt <= 4'd0;
        crc_done <= 1'b0;
    end else if (crc_en) begin
        // CRC计算逻辑(CAN CRC-15多项式:x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1)
        crc_reg[0] <= data_bit ^ crc_reg[CRC_LEN];
        crc_reg[1] <= crc_reg[0];
        crc_reg[2] <= crc_reg[1];
        crc_reg[3] <= crc_reg[2] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[4] <= crc_reg[3] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[5] <= crc_reg[4];
        crc_reg[6] <= crc_reg[5];
        crc_reg[7] <= crc_reg[6] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[8] <= crc_reg[7] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[9] <= crc_reg[8];
        crc_reg[10] <= crc_reg[9] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[11] <= crc_reg[10];
        crc_reg[12] <= crc_reg[11];
        crc_reg[13] <= crc_reg[12];
        crc_reg[14] <= crc_reg[13] ^ data_bit ^ crc_reg[CRC_LEN];
        crc_reg[15] <= crc_reg[14] ^ data_bit ^ crc_reg[CRC_LEN];

        // 计数到总位数后完成
        if (bit_cnt == total_bits - 1) begin
            crc_done <= 1'b1;
            bit_cnt <= 4'd0;
        end else begin
            bit_cnt <= bit_cnt + 1'b1;
            crc_done <= 1'b0;
        end
    end else begin
        crc_done <= 1'b0;
    end
end

// 计算需CRC的总位数(标准数据帧)
always_comb begin
    total_bits = 1 + STD_ID_LEN + 1 + 1 + 1 + DLC_LEN + (8 * dlc);  // SOF + ID + RTR + IDE + R0 + DLC + 数据
end

input logic [DLC_LEN-1:0] dlc;  // 数据长度(来自控制模块)

endmodule

4. 发送模块(CAN Transmitter)

功能:构建标准数据帧,实现位填充、CRC 计算、仲裁逻辑。

verilog

复制代码
module can_transmitter(
    input  logic clk,          // 系统时钟(50MHz)
    input  logic rst_n,        // 复位信号(低电平有效)
    input  logic tq_clk,       // TQ时钟
    input  logic sync_seg,     // 同步段标志
    input  logic phase_seg1,   // 相位缓冲段1标志
    input  logic phase_seg2,   // 相位缓冲段2标志
    input  logic bit_end,      // 位结束标志
    input  logic can_rx,       // CAN接收信号(用于仲裁)
    input  logic tx_en,        // 发送使能
    input  logic [STD_ID_LEN-1:0] std_id,  // 标准ID
    input  logic rtr,          // 远程帧请求(0=数据帧,1=远程帧)
    input  logic [DLC_LEN-1:0] dlc,        // 数据长度
    input  logic [DATA_MAX_LEN*8-1:0] data,// 发送数据
    output logic can_tx,       // CAN发送信号(到收发器)
    output logic tx_done,      // 发送完成标志
    output logic tx_error      // 发送错误标志
);

can_state_t state = IDLE;
logic [3:0] bit_cnt = 4'd0;    // 字段内位计数器
logic [7:0] data_byte = 8'd0;  // 当前发送的数据字节
logic [CRC_LEN-1:0] crc_val;   // CRC值
logic crc_en;                  // CRC使能
logic crc_done;                // CRC完成
logic [5:0] stuff_cnt = 6'd0;  // 位填充计数器
logic stuff_bit = 1'b0;        // 填充位

// 实例化CRC模块
can_crc u_crc(
    .clk(clk),
    .rst_n(rst_n),
    .crc_en(crc_en),
    .data_bit(data_bit),
    .crc_out(crc_val),
    .crc_done(crc_done),
    .dlc(dlc)
);

// 状态机控制
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        bit_cnt <= 4'd0;
        data_byte <= 8'd0;
        can_tx <= 1'b1;  // 空闲时为隐性电平(1)
        tx_done <= 1'b0;
        tx_error <= 1'b0;
        stuff_cnt <= 6'd0;
        stuff_bit <= 1'b0;
        crc_en <= 1'b0;
    end else begin
        case (state)
            IDLE: begin
                tx_done <= 1'b0;
                tx_error <= 1'b0;
                stuff_cnt <= 6'd0;
                if (tx_en) begin
                    state <= SYNC;
                    bit_cnt <= 4'd0;
                    // 准备发送SOF(显性电平0)
                    can_tx <= 1'b0;
                    // 启动CRC计算(SOF为第一个数据位)
                    crc_en <= 1'b1;
                    data_bit <= 1'b0;
                end else begin
                    can_tx <= 1'b1;
                end
            end

            SYNC: begin
                if (bit_end) begin
                    state <= ARBITRATION;
                    bit_cnt <= 4'd0;
                    // 发送ID的最高位
                    can_tx <= std_id[STD_ID_LEN-1 - bit_cnt];
                    data_bit <= std_id[STD_ID_LEN-1 - bit_cnt];
                    // 仲裁:检测总线电平是否与发送电平一致
                    if (can_rx != can_tx) begin
                        // 仲裁失败,转为接收
                        state <= IDLE;
                        tx_error <= 1'b1;
                    end
                end else begin
                    // 位填充检测
                    if (can_tx == stuff_bit_prev) begin
                        stuff_cnt <= stuff_cnt + 1'b1;
                        if (stuff_cnt == 5'd5) begin
                            // 连续5个相同电平,插入填充位(相反电平)
                            can_tx <= ~can_tx;
                            stuff_cnt <= 6'd0;
                            stuff_bit <= 1'b1;
                            crc_en <= 1'b0;  // 填充位不参与CRC
                        end
                    end else begin
                        stuff_cnt <= 6'd0;
                        stuff_bit <= 1'b0;
                        crc_en <= 1'b1;
                    end
                end
            end

            // 后续状态(ARBITRATION、CONTROL、DATA、CRC、ACK、EOF)类似,按帧结构发送对应字段
            // 此处省略详细状态跳转,核心逻辑:按帧字段顺序发送,位填充,仲裁检测,CRC计算
            // ...

            EOF: begin
                if (bit_end) begin
                    state <= IDLE;
                    tx_done <= 1'b1;
                end
            end

            default: state <= IDLE;
        endcase
    end
end

logic stuff_bit_prev;
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        stuff_bit_prev <= 1'b1;
    end else begin
        stuff_bit_prev <= can_tx;
    end
end

logic data_bit;  // 输入到CRC的数据位
always_comb begin
    case (state)
        SYNC: data_bit <= 1'b0;  // SOF
        ARBITRATION: data_bit <= std_id[STD_ID_LEN-1 - bit_cnt];  // ID位
        CONTROL: data_bit <= (bit_cnt == 0) ? 1'b0 : (bit_cnt == 1) ? 1'b0 : (bit_cnt >= 2 && bit_cnt < 2+DLC_LEN) ? dlc[DLC_LEN-1 - (bit_cnt-2)] : 1'b0;  // IDE=0,R0=0,DLC位
        DATA: data_bit <= data_byte[7 - (bit_cnt % 8)];  // 数据位
        default: data_bit <= 1'b0;
    endcase
end

endmodule

5. 接收模块(CAN Receiver)

功能:解析 CAN 帧,实现位同步、位填充解除、CRC 校验、应答检测。

verilog

复制代码
module can_receiver(
    input  logic clk,          // 系统时钟(50MHz)
    input  logic rst_n,        // 复位信号(低电平有效)
    input  logic tq_clk,       // TQ时钟
    input  logic sync_seg,     // 同步段标志
    input  logic phase_seg1,   // 相位缓冲段1标志
    input  logic phase_seg2,   // 相位缓冲段2标志
    input  logic bit_end,      // 位结束标志
    input  logic can_rx,       // CAN接收信号(来自收发器)
    output logic rx_done,      // 接收完成标志
    output logic rx_error,     // 接收错误标志
    output logic [STD_ID_LEN-1:0] rx_std_id,  // 接收标准ID
    output logic rx_rtr,       // 接收RTR位
    output logic [DLC_LEN-1:0] rx_dlc,        // 接收DLC
    output logic [DATA_MAX_LEN*8-1:0] rx_data// 接收数据
);

can_state_t state = IDLE;
logic [3:0] bit_cnt = 4'd0;    // 字段内位计数器
logic [7:0] data_byte = 8'd0;  // 当前接收的数据字节
logic [CRC_LEN-1:0] crc_val;   // 接收的CRC值
logic [CRC_LEN-1:0] calc_crc;  // 计算的CRC值
logic crc_en;                  // CRC计算使能
logic crc_done;                // CRC完成
logic [5:0] stuff_cnt = 6'd0;  // 位填充计数器
logic stuff_bit = 1'b0;        // 填充位标志

// 实例化CRC模块(用于校验)
can_crc u_crc(
    .clk(clk),
    .rst_n(rst_n),
    .crc_en(crc_en),
    .data_bit(can_rx),
    .crc_out(calc_crc),
    .crc_done(crc_done),
    .dlc(rx_dlc)
);

// 状态机控制
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        bit_cnt <= 4'd0;
        data_byte <= 8'd0;
        rx_done <= 1'b0;
        rx_error <= 1'b0;
        rx_std_id <= {STD_ID_LEN{1'b0}};
        rx_rtr <= 1'b0;
        rx_dlc <= {DLC_LEN{1'b0}};
        rx_data <= {DATA_MAX_LEN*8{1'b0}};
        stuff_cnt <= 6'd0;
        stuff_bit <= 1'b0;
        crc_en <= 1'b0;
    end else begin
        case (state)
            IDLE: begin
                rx_done <= 1'b0;
                rx_error <= 1'b0;
                stuff_cnt <= 6'd0;
                // 检测SOF(显性电平0)
                if (can_rx == 1'b0 && sync_seg) begin
                    state <= SYNC;
                    bit_cnt <= 4'd0;
                    // 启动CRC计算(SOF为第一个数据位)
                    crc_en <= 1'b1;
                end
            end

            SYNC: begin
                if (bit_end) begin
                    state <= ARBITRATION;
                    bit_cnt <= 4'd0;
                end
                // 位填充解除:检测连续5个相同电平,跳过第6个填充位
                if (can_rx == stuff_bit_prev) begin
                    stuff_cnt <= stuff_cnt + 1'b1;
                    if (stuff_cnt == 5'd5) begin
                        stuff_bit <= 1'b1;  // 标记填充位,下一位跳过
                        stuff_cnt <= 6'd0;
                    end
                end else begin
                    stuff_cnt <= 6'd0;
                    stuff_bit <= 1'b0;
                end
            end

            ARBITRATION: begin
                if (bit_end && !stuff_bit) begin
                    if (bit_cnt == STD_ID_LEN - 1) begin
                        // 接收完ID,下一位是RTR
                        rx_std_id[STD_ID_LEN-1 - bit_cnt] <= can_rx;
                        state <= CONTROL;
                        bit_cnt <= 4'd0;
                    end else begin
                        rx_std_id[STD_ID_LEN-1 - bit_cnt] <= can_rx;
                        bit_cnt <= bit_cnt + 1'b1;
                    end
                end else if (stuff_bit) begin
                    stuff_bit <= 1'b0;  // 跳过填充位
                end
                // CRC继续计算(ID位)
                crc_en <= !stuff_bit;
            end

            // 后续状态(CONTROL、DATA、CRC、ACK、EOF)类似,按帧结构接收对应字段,位填充解除,CRC校验
            // ...

            EOF: begin
                if (bit_end) begin
                    state <= IDLE;
                    rx_done <= 1'b1;
                    // CRC校验
                    if (calc_crc != crc_val) begin
                        rx_error <= 1'b1;
                    end
                end
            end

            default: state <= IDLE;
        endcase
    end
end

logic stuff_bit_prev;
always_ff @(posedge tq_clk or negedge rst_n) begin
    if (!rst_n) begin
        stuff_bit_prev <= 1'b1;
    end else begin
        stuff_bit_prev <= can_rx;
    end
end

endmodule

6. 顶层模块(CAN Top Module)

功能:整合位时序、发送、接收模块,提供与 CAN 收发器的物理接口和外部配置接口。

verilog

复制代码
module can_top(
    input  logic clk,          // 系统时钟(50MHz)
    input  logic rst_n,        // 复位信号(低电平有效)
    input  logic tx_en,        // 发送使能
    input  logic [STD_ID_LEN-1:0] std_id,  // 发送标准ID
    input  logic rtr,          // 发送RTR位
    input  logic [DLC_LEN-1:0] dlc,        // 发送DLC
    input  logic [DATA_MAX_LEN*8-1:0] data,// 发送数据
    output logic can_tx,       // CAN发送信号(到TJA1050 TXD)
    input  logic can_rx,       // CAN接收信号(来自TJA1050 RXD)
    output logic tx_done,      // 发送完成标志
    output logic tx_error,     // 发送错误标志
    output logic rx_done,      // 接收完成标志
    output logic rx_error,     // 接收错误标志
    output logic [STD_ID_LEN-1:0] rx_std_id,  // 接收标准ID
    output logic rx_rtr,       // 接收RTR位
    output logic [DLC_LEN-1:0] rx_dlc,        // 接收DLC
    output logic [DATA_MAX_LEN*8-1:0] rx_data// 接收数据
);

// 中间信号
logic tq_clk;
logic sync_seg;
logic prop_seg;
logic phase_seg1;
logic phase_seg2;
logic bit_start;
logic bit_end;

// 实例化位时序模块
can_bit_timing u_bit_timing(
    .clk(clk),
    .rst_n(rst_n),
    .can_rx(can_rx),
    .tq_clk(tq_clk),
    .sync_seg(sync_seg),
    .prop_seg(prop_seg),
    .phase_seg1(phase_seg1),
    .phase_seg2(phase_seg2),
    .bit_start(bit_start),
    .bit_end(bit_end)
);

// 实例化发送模块
can_transmitter u_transmitter(
    .clk(clk),
    .rst_n(rst_n),
    .tq_clk(tq_clk),
    .sync_seg(sync_seg),
    .phase_seg1(phase_seg1),
    .phase_seg2(phase_seg2),
    .bit_end(bit_end),
    .can_rx(can_rx),
    .tx_en(tx_en),
    .std_id(std_id),
    .rtr(rtr),
    .dlc(dlc),
    .data(data),
    .can_tx(can_tx),
    .tx_done(tx_done),
    .tx_error(tx_error)
);

// 实例化接收模块
can_receiver u_receiver(
    .clk(clk),
    .rst_n(rst_n),
    .tq_clk(tq_clk),
    .sync_seg(sync_seg),
    .phase_seg1(phase_seg1),
    .phase_seg2(phase_seg2),
    .bit_end(bit_end),
    .can_rx(can_rx),
    .rx_done(rx_done),
    .rx_error(rx_error),
    .rx_std_id(rx_std_id),
    .rx_rtr(rx_rtr),
    .rx_dlc(rx_dlc),
    .rx_data(rx_data)
);

endmodule

四、硬件验证

1. 硬件连接

  • FPGA ↔ CAN 收发器(TJA1050)
    • FPGA 的can_tx → TJA1050 的TXD
    • FPGA 的can_rx → TJA1050 的RXD
    • FPGA 的VCC(3.3V)→ TJA1050 的VCC
    • FPGA 的GND → TJA1050 的GND
  • CAN 收发器 ↔ CAN 总线
    • TJA1050 的CAN_H → CAN 总线CAN_H
    • TJA1050 的CAN_L → CAN 总线CAN_L
    • CAN 总线两端接 120Ω 终端电阻;
  • 其他:FPGA 需提供 50MHz 时钟和复位信号(可通过按键或外部电路实现)。

2. 验证步骤

  1. 参数配置:通过 FPGA 的配置接口(如 JTAG)设置发送参数(ID=0x001,DLC=2,数据 = 0x1234,波特率 = 500kHz);
  2. 发送数据 :触发tx_en信号,FPGA 生成 CAN 标准数据帧并发送到总线;
  3. 接收验证
    • 若总线上有其他 CAN 节点(如 CANoe 仿真节点、另一块 FPGA 开发板),可接收并解析该帧,验证 ID、数据、DLC 是否正确;
    • 若只有一块 FPGA,可将can_tx直接连接到can_rx(回环测试),FPGA 接收自己发送的数据,通过rx_donerx_data确认接收正确;
  4. 错误测试 :故意修改发送数据,验证rx_error是否能检测到 CRC 错误。

五、关键注意事项与扩展功能

1. 注意事项

  • 时序精确性:CAN 位时序对传输可靠性至关重要,需严格按照协议规范设计 TQ 时钟和位段划分,避免非整数分频导致的时序误差;
  • 位填充与解除:发送时需正确插入填充位,接收时需准确解除填充,否则会导致帧解析错误;
  • 仲裁机制:多节点通信时,需确保仲裁逻辑正确,避免总线冲突;
  • 收发器匹配:CAN 收发器的电平需与 FPGA 的 IO 电平匹配(通常为 3.3V),否则需添加电平转换电路;
  • 终端电阻:CAN 总线必须接终端电阻,否则信号反射会导致通信失败。

2. 扩展功能

  • 扩展帧支持:在帧结构中添加 29 位扩展 ID,修改仲裁场和控制场的处理逻辑;
  • 多帧发送 / 接收:添加 FIFO 缓冲区,支持连续多帧数据的收发,避免数据丢失;
  • ID 过滤:接收模块添加 ID 过滤逻辑,只接收特定 ID 的帧,减少无效数据处理;
  • 错误管理:完善错误处理模块,支持错误计数器管理和总线关闭恢复;
  • 波特率自适应:设计波特率检测逻辑,支持自动识别总线波特率。

六、总结

FPGA 实现 CAN 接口的核心是严格遵循 CAN 协议的时序和帧结构 ,通过硬件逻辑实现位时序生成、帧构建与解析、仲裁、错误处理等功能。相比单片机的 CAN 控制器,FPGA 的硬件实现具有实时性强、可定制性高、并行处理能力强的优势,适用于对通信速率和可靠性要求较高的场景。实际应用中,可根据需求扩展扩展帧、ID 过滤、多帧收发等功能,适配复杂的 CAN 总线系统。

相关推荐
与光同尘 大道至简2 小时前
ESP32 小智 AI 机器人入门教程从原理到实现(自己云端部署)
人工智能·python·单片机·机器人·github·人机交互·visual studio
漫随流水2 小时前
leetcode算法(513.找树左下角的值)
数据结构·算法·leetcode·二叉树
老李的森林3 小时前
嵌入式开发--无刷电机FOC控制--用定时器事件驱动ADC采样
stm32·单片机·嵌入式硬件·foc·无刷电机
囊中之锥.3 小时前
机器学习算法详解:DBSCAN 聚类原理、实现流程与优缺点分析
算法·机器学习·聚类
一路往蓝-Anbo3 小时前
【第42期】调试进阶(一):IDE中的Register与Memory窗口
c语言·开发语言·ide·stm32·单片机·嵌入式硬件
AlenTech3 小时前
152. 乘积最大子数组 - 力扣(LeetCode)
算法·leetcode·职场和发展
Piar1231sdafa3 小时前
基于yolo13-C3k2-RVB的洗手步骤识别与检测系统实现_1
人工智能·算法·目标跟踪
boneStudent3 小时前
STM32工业HMI控制系统
stm32·单片机·嵌入式硬件
做科研的周师兄3 小时前
【MATLAB 实战】|多波段栅格数据提取部分波段均值——批量处理(NoData 修正 + 地理信息保真)_后附完整代码
前端·算法·机器学习·matlab·均值算法·分类·数据挖掘