基于FPGA的轻量级CAN总线控制器实现方案

科创之家 2026-04-03 8485人围观

简介

CAN总线作为工业和汽车领域最常用的通信总线,具有拓扑结构简洁、可靠性高、传输距离长等优点。CAN总线的非破坏性仲裁机制依赖于帧ID,CAN2.0A和CAN2.0B分别规定了11bit-ID(短ID)的标准帧和29bit-ID(长ID)的扩展帧,另外,还有远程帧这种数据请求机制。关于CAN总线的更多知识可以参考这个科普文章。

本库实现了一个轻量化但完备的FPGA CAN总线控制器,特点如下:

平台无关:纯 Verilog 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。

本地ID可固定配置为任意短ID。

发送: 仅支持以本地ID发送数据长度为4Byte的帧。

接收: 支持接收短ID或长ID的帧,接收帧的数据长度没有限制 (即支持0~8Byte) 。

接收帧过滤: 可针对短ID和长ID独立设置过滤器,只接收和过滤器匹配的数据帧。

自动响应远程帧: 当收到的远程帧与本地ID匹配时,自动将发送缓存中的下一个数据发送出去。若缓存为空,则重复发送上次发过的数据。

设计文件

RTL 文件夹包含3个设计代码文件,各文件功能如下表。你只需将这3个文件包含进工程,就可以调用顶层模块can_top.v进行CAN通信业务的开发。

文件名 功能 备注
can_top.v CAN控制器的顶层
can_level_packet.v 帧级控制器,负责解析或生成帧,并实现非破坏性仲裁 被can_top.v调用
can_level_bit.v 位级控制器,负责收发bit,具有抗频率偏移的下降沿对齐机制 被can_level_packet.v调用

仿真文件

仿真相关的文件都在 SIM 文件夹中,其中:

tb_can_top.v 是针对 can_top.v 的 testbench 。

tb_can_top_run_iverilog.bat 包含了运行 iverilog 仿真的命令。

tb_can_top.v 描述了4个CAN总线设备互相进行通信的场景,每个设备都是一个 can_top 的例化,图1是每个设备的详细属性,各个设备互相接收的关系可以画成左侧的图,箭头代表了各个设备之间的接收关系。另外,每个CAN设备的驱动时钟并不严格是50MHz,而是有不同的±1%的偏移,这是为了模拟更糟糕的实际情况下,CAN控制器的“自动对齐”机制能否奏效。

394bbef0-2d97-11f1-90a1-92fbcf53809c.png

图1:仿真中的4个CAN设备的详细参数

使用 iverilog 进行仿真前,需要安装 iverilog ,见:iverilog_usage

然后双击 tb_can_top_run_iverilog.bat 运行仿真。仿真运行完后,可以打开生成的 dump.vcd 文件查看波形。

顶层模块说明

本节介绍如何使用can_top.v,它的接口如下表。

信号 方向 宽度 功能 备注
rstn 输入 1 低电平复位 在开始工作前需要拉低复位一下
clk 输入 1 驱动时钟 频率需要是CAN总线波特率的10倍以上,内部分频产生波特率
can_rx 输入 1 CAN-PHY RX 应通过FPGA的普通IO引出,接CAN-PHY芯片 (例如TJA1050)
can_tx 输出 1 CAN-PHY TX 应通过FPGA的普通IO引出,接CAN-PHY芯片 (例如TJA1050)
tx_valid 输入 1 发送有效 当=1时,若发送缓存未满(即tx_ready=1),则tx_data被送入发送缓存
tx_ready 输出 1 发送就绪 当=1时,说明发送缓存未满。与 tx_valid 构成一对握手信号
tx_data 输入 32 发送数据 当tx_valid=1时需要同步给出待发送数据 tx_data
rx_valid 输出 1 接收有效 当=1时,rx_data上产生1字节的有效接收数据
rx_last 输出 1 接收最后字节指示 当=1时,说明当前的rx_data是一个帧的最后一个数据字节
rx_data 输出 8 接收数据 当rx_valid=1时,rx_data上产生1字节的有效接收数据
rx_id 输出 29 接收ID 指示当前接收帧的ID,若为短ID则低11bit有效
rx_ide 输出 1 接收ID类型 =1 说明当前接收帧是长ID,否则为短ID

接入CAN总线

can_top.v的can_rx和can_tx接口需要引出到FPGA引脚上,并接CAN-PHY芯片(比如TJA1050),如图2。

图2:接入CAN总线的方式

注:这里注意一个坑,虽然FPGA的引脚(can_rx,can_tx)可以是3.3V电平的,但CAN-PHY的电源必须是5V的,否则对CAN总线的驱动力不够。另外,CAN-PHY要和FPGA共地。

用户发送接口

can_top.v的tx_valid,tx_ready,tx_data构成了流式输入接口,它们都与clk的上升沿对齐,用于向发送缓存中写入一个数据。只要发送缓冲区不为空,其中的数据会逐个被CAN控制器发送到CAN总线上。

tx_valid和tx_ready是一对握手信号,波形如下图,只有当tx_valid和tx_ready都为1时,tx_data才被写入缓存。tx_ready=0说明缓存已满,此时即使tx_valid=1,也无法写入缓存。不过,当发送频率不高而不至于让 CAN 总线达到饱和时,可以不用考虑缓存满(即tx_ready=0)的情况。

下图中,D0,D1,D2这3个数据被写入缓存,D0写入后,缓存已满,导致tx_ready=0,之后的3个周期D1都没有成功写入,但在第4个时钟周期tx_ready变成1,D1被写入。之后发送方主动空闲2个时钟周期后,D3也被写入。

每个数据都是4Byte(32bit)的,只要FIFO不为空,该CAN控制器就自动地每次发送一个帧,每帧一个数据,帧数据长度为4Byte。

     _  __  __  __  __  __  __  __  __  __  __  __ clk    \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/            _____________________________       _____tx_valid ___________/               \___________/   \________     _________________          ________________________________tx_ready          \_________________/           _____ _______________________       _____tx_data  XXXXXXXXXXXX__D0_X___________D1__________XXXXXXXXXXXXX__D3_XXXXXXXXX

用户接收接口

can_top.v的rx_valid,rx_last,rx_data,rx_id,rx_ide构成了接收接口,它们都与clk的上升沿对齐。

当CAN总线上收到了一个与ID过滤器匹配的数据帧后,会将这一帧的字节逐个发送出来。设数据帧长为n字节,则rx_valid上会连续产生n个周期的高电平,同时rx_data上每拍时钟会产生一个收到的数据字节,在最后一拍会让rx_last=1,指示一帧的结束。在整个过程中,rx_id上出现该帧的ID(若为短ID,则只有低11bit有效),同时,rx_ide指示该帧为长ID还是短ID。

接收接口的波形图举例如下,该例中模块先后收到了一个短ID的,数据长度为4的数据帧,和一个长ID的,数据长度为2的数据帧。

      __  __  __  __  __  __  __  __  __  __  __ clk   __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_          _______________________       ___________rx_valid _________/            \___________/      \_________                   _____          _____rx_last  ___________________________/   \_________________/   \_________          _____ _____ _____ _____       _____ _____rx_data  XXXXXXXXXX__0__X__1__X__2__X__3__XXXXXXXXXXXXX__0__X__1__XXXXXXXXXX          _______________________       ___________rx_id   XXXXXXXXXX__________ID1__________XXXXXXXXXXXXX____ID2____XXXXXXXXXX                            _____________________rx_ide  _____________________________________________/

与发送接口不同,接收接口无握手机制,只要收到数据就让rx_valid=1来发出,不会等待接收方是否能接受。

配置本地ID

can_top.v有一个11bit的参数(parameter)叫LOCAL_ID,它决定了该模块发送的帧的ID;同时,当can_top接收的远程帧的ID与LOCAL_ID匹配时,就会进行响应(ACK),并自动回复一个数据帧(如果发送缓存为空,则自动重复回复上次发过的数据)。

配置ID过滤器

can_top.v的RX_ID_SHORT_FILTER和RX_ID_SHORT_MASK参数用来配置短ID过滤器。设收到的数据帧的ID是rxid(短),匹配表达式为:

rxid & RX_ID_SHORT_MASK == RX_ID_SHORT_FILTER & RX_ID_SHORT_MASK

以上表达式满足 Verilog 或 C 语言的语法。表达式为真时,rxid与过滤器匹配,这样的数据帧才能被模块响应(ACK),并将其数据转发到用户接受接口上。表达式为假时,rxid与过滤器不匹配,该数据帧不仅不会被响应(ACK),也不会被转发到用户接受接口上。

同理,RX_ID_LONG_FILTER和RX_ID_LONG_MASK参数用来配置长ID过滤器。设收到的数据帧的ID是rxid(长),匹配表达式为:

rxid & RX_ID_LONG_MASK == RX_ID_LONG_FILTER & RX_ID_LONG_MASK

MASK参数可以被称为通配掩码,掩码=1的位必须匹配FILTER,掩码=0的位我们不在乎,既可以匹配1也可以匹配0。

例如你想接收 0x122, 0x123, 0x1a2, 0x1a3 这4种短ID,则可配置为:

RX_ID_SHORT_FILTER = 11'h122,RX_ID_SHORT_MASK  = 11'h17e

配置时序参数

can_top.v的default_c_PTS,default_c_PBS1,default_c_PBS2这3个时序参数决定了CAN总线上的一个位的3个段(PTS段, PBS1段, PBS2段)的默认长度,这3个段的含义详见这个科普文章。总的来说,分频系数计算如下:

分频系数 = default_c_PTS + default_c_PBS1 + default_c_PBS2 + 1

而CAN总线的波特率计算方法为:

CAN波特率 = clk频率 / 分频系数

例如,在 clk=50MHz的情况下,可以使用如下参数组合来配置出各种常见的波特率。

分频系数 波特率 default_c_PTS default_c_PBS1 default_c_PBS2
50 1MHz 16'd34 16'd5 16'd10
100 500kHz 16'd69 16'd10 16'd20
500 100kHz 16'd349 16'd50 16'd100
5000 10kHz 16'd3499 16'd500 16'd1000
10000 5kHz 16'd6999 16'd1000 16'd2000

示例程序

FPGA 目录里的fpga_top.v是一个调用can_top.v进行简单的 CAN 通信的案例。要运行该案例,请建立 FPGA 工程,并加入以下源文件:

FPGA 目录里的 fpga_top.v 、 uart_tx.v 。

RTL 目录里的 can_top.v 、 can_level_packet.v 、 can_level_bit.v 。

fpga_top.v 是项目的顶层,其引脚连接方法如下:

module fpga_top (  // clock ,连接到 FPGA 板上晶振,频率必须为 50MHz   input wire      CLK50M,  // UART (TX only), 连接到电脑串口(比如通过 USB 转 UART 模块),不方便接 UART 可以不接  outputwire      UART_TX,  // CAN bus, 连接到 CAN PHY 芯片,然后 CAN PHY 连接到 CAN 总线(如图2)  input wire      CAN_RX,  outputwire      CAN_TX);

该案例的行为是:

本地 (也就是 FPGA) 的 CAN ID 配置为 0x456 ,因此所有发出的数据帧的 ID 都为 0x456 。

ID 过滤器配置为只接收短ID=0x123或长ID=0x12345678的数据帧。

每一秒向发送缓存中送入一个递增的数据,该数据帧并会被发送到 CAN 总线上。

将 CAN 总线上接收到的(也就是符合过滤器配置)的数据帧通过 UART 发送给电脑。

注:该案例中,CAN 的波特率为 1MHz ; UART 的格式为 115200,8,n,1

3f61610a-2d97-11f1-90a1-92fbcf53809c.jpg

图3:硬件连接

我在测试该例子时,将 CAN 总线与一台USB-CAN调试器相连,如图3。然后编译工程并下载FPGA,打开USB-CAN调试器的配套软件,可以看到如下现象:

软件中每秒会收到一个 FPGA 发来的帧,数据长度DLC=4,值递增。如图4中没框的部分。

在软件中发送短ID=0x123或长ID=0x12345678的数据帧,会显示“发送成功”,如图4中蓝框的部分,说明该帧被 FPGA 响应了。同时,如果你把FPGA的UART连接到了电脑的串口,则可以在“串口助手”或“HyperTerminal”等软件上看到打印出的数据内容。

在软件中发送短ID=0x456的远程帧,FPGA 会立即响应一个数据帧,如图4中红框的部分。

图4:USB-CAN调试器的配套软件上观察到的现象

  • 随机文章
  • 热门文章
  • 热评文章
不容错过
Powered By Z-BlogPHP