基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

基于FPGA的CAN总线控制器的设计(附代码)

导读

CAN 总线(Controller Area Network)是控制器局域网的简称,是 20 世纪 80 年代初德国 BOSCH 公司为解决现代汽车中众多的控制与测试仪器之间的数据交换而开发的一种串行数据通信协议。目前,CAN 总线已经被列入 ISO 国际标准,称为 ISO11898。CAN 总线已经成为工业数据通信的主流技术之一。

CAN 总线作为数字式串行通信技术,与其他同类技术相比,在可靠性、实时性和灵活性方面具有独特的技术优势,主要特点如下:

  •  CAN 总线是一种多主总线,总线上任意节点可在任意时刻主动地向网络上其他节点发送信息而不分主次,因此可在各节点之间实现自由通信。
  •  CAN 总线采用非破坏性总线仲裁技术。当多个节点同时向总线发送信息时,优先级低的节点会主动退出发送,而最高优先级的节点可以不受影响地继续传输数据,从而大大节省总线冲突的仲裁时间。即使在网络负载很重的情况下也不会发生网络瘫痪情况。
  • CAN 总线的通信介质可以是双绞线、同轴电缆或光导纤维,选择灵活。
  • CAN 总线的通信速率可达 1Mbit/s(此时通信距离最长为 40 米),通信距离最远可达 10km(速率在 5kbit/s 以下)。
  •  CAN 总线上的节点信息分成不同的优先级,可以满足不同级别的实时要求,高优先级的数据可以在 134μs 内得到传输。
  • CAN 总线通过报文滤波即可实现点对点、一点对多点及全局广播等几种方式传送数据,无需专门的调度。
  •  CAN 总线的数据采用短帧结构,传输时间短,受干扰概率低,具有极好的检错效果。
  • CAN 总线采用 CRC 检验并可提供相应的错误处理功能,保证了数据通信的可靠性。
  • CAN 总线上的器件可被置于无任何内部活动的睡眠方式,相当于未连接到总线上,可以有效降低系统功耗。

CAN 总线上的节点在错误严重的情况下具有自动关闭输出的功能,以使总线上其他节点的操作不受影响。CAN 总线卓越的特性、极高的可靠性和独特的设计,特别适合工业过程中监控设备的互连,因此,越来越受到工业界的重视,并被公认为是最有前途的现场总线之一。另外,CAN 总线协议已被国际标准化组织认可,技术比较成熟,控制的芯片已经商品化,性价比高,特别适用于分布式测控系统之间的数通讯。

CAN 总线插卡可以任意插在 PC AT XT 兼容机上,方便地构成分布式监控系统。因此,用 FPGA 实现 CAN 总线通信控制器具有非常重要的应用价值。本篇将通过一个实例讲解利用 FPGA 实现 CAN 总线通信控制器的实现方法。

第一篇内容摘要:本篇会介绍CAN 总线协议解析,包括CAN 总线通信模型、CAN 总线协议中的基本概念、报文的数据结构、 位时序(Bit Timing)、同步(Synchronization)等相关内容。还会介绍CAN 通信控制器程序基本框架,包括SJA1000CAN 通信控制器、CAN 通信控制器程序框架等相关内容。

一、CAN 总线协议解析

在讲解实现 CAN 总线的实例以前,读者需要具备有关 CAN 总线的基本知识。为此,将在这里简要介绍与实例相关的基础知识。

1.1 CAN 总线通信模型

参照 ISO/OSI 标准模型,CAN 总线的通信参考模型如图 1 所示。

图片[1]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 1 CAN 总线通信模型

这 4 层结构的功能如下:

• 物理层规定了节点的全部电气特性,在一个网络里,要实现不同节点间的数据传输,所有节点的物理层必须是相同的。

• 传输层描述了 CAN 总线协议的内核,它负责位时序(bit timing)、同步、仲裁、应答、错误探测等。

• 对象层负责报文的过滤、状态和控制。

• 应用层完成用户指定的数据传输任务。

CAN 总线的物理层为数据通信提供了物理连接,而实际的数据通信在其他 3 层中完成。

1.2 CAN 总线协议中的基本概念

在讲解 CAN 总线协议之前,需要介绍有关协议中的基本概念。

1.报文(Messages)

在 CAN 总线传输的数据具有固定的格式和有限的长度,称为报文。

2.发送器(Transmitter)和接收器(Receiver)

在 CAN 总线的数据传输过程中,发出报文的节点称为发送器。节点在总线进入空闲状态前或丢失仲裁前为发送器。如果一个节点不是报文发送器,并且总线不处于空闲状态,则该节点为接收器。

3.比特率(bit rate)

CAN 总线的输出速度以单位时间内传输的位来衡量,称为比特率。CAN 总线在不同的系统中可以有不同的比特率。但是在给定的系统中,比特率是统一的和固定的。

4.优先级(Priorities)

优先级表示总线传输中一个报文的优先级别。

5.远程数据请求(Remote Data Request)

当一个节点向另一个节点请求数据时,需要首先发送一个远程帧(Remote Frame),然后发送一个和远程帧相符的数据帧(Data Frame)。远程帧和数据帧具有相同的标识符。

6.位流(Bit Stream)

CAN 总线通信过程中的数据流。

7.编码方式CAN 总线通信协议规定,报文中的位流按照非归零(Non-Return to Zero)码的方法编码,一个完整的电平要么是显性,要么是隐性。

8.非归零编码(Non-Return to Zero encoding,简称 NRZ)

非归零编码是一种用在低速通信接口中的编码方式,同时提供同步和非同步两种方式。在非归零编码方式中,逻辑“1”在传输过程中用一位高电平表示,逻辑“0”用一位低电平表示。非归零编码方式如图 2 所示。

图片[2]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 2 非归零编码方式

9.总线数值

在数据传输时,CAN 总线有两种逻辑值:显性值(dominant)和隐性值(recessive)。如果同时传输显性值和隐性值时,总线上的最终结果是显性值。在线与(wired-AND)总线连接方式中,显性值用逻辑“0”表示,隐性值用逻辑“1”表示。

1.3 报文的数据结构

CAN 总线的报文传输是通过 4 种不同类型的帧(Frame)来表示和控制的:

• 数据帧(Data Frame) 用来在数据传输过程中携带数据。

• 远程帧(Remoter Frame) 接收器发送远程帧来请求发送器发送数据,具有和数据帧同样的标识符。

• 出错帧(Error Frame) 用来检测 CAN 总线数据传输过程中的错误。

• 超载帧(Overload Frame) 用于提供当前和后续数据帧或远程帧之间的附加延迟。

a.数据帧

数据帧的具体组成如图 3 所示。

图片[3]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 3 数据帧的具体组成

数据帧由帧起始(Start of Frame,SOF)、仲裁字段、控制字段、数据字段、CRC 字段、ACK 字段(应答字段)和帧尾组成。

帧起始标志数据帧的开始(远程帧同样具有帧起始),它仅由一个显性值组成。只有在总线处于空闲时,才允许节点开始发送。所有节点必须与首先开始发送的那个节点的帧起始位前沿同步。

仲裁字段由标识符和远程发送请求位(RTR 位)组成,如图 4 所示。标识符的长度为 11位。远程发送请求位在数据帧中必须是显性值,在远程帧中必须是隐性值。

图片[4]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 4 仲裁字段的组成

控制字段由保留位和数据长度码组成,如图 5 所示。数据长度码表示数据字段的长度。

图片[5]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 5 控制字段的组成

数据字段由数据帧中被发送的数据组成,它可以包括 0~8 个字节,每个字节 8 位。首先发送的是最高有效位。

CRC 字段包括 CRC 序列和 CRC 界定符。CRC 序列用来实现 CRC 计算,CRC 界定符只包括一个隐性值。应答字段为两位,包括应答间隙和应答界定符。帧尾由 7 个连续的隐性值组成,作为数据帧和远程帧的结束标志。

b.远程帧

作为接收器的节点可以通过向相应的数据源节点发送一个远程帧来激活该节点,让它把数据发送给接收器。远程帧由帧起始、仲裁字段、控制字段、CRC 字段、应答字段和帧尾 6 个不同的字段组成。远程帧的组成如图 6 所示。

图片[6]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 6 远程帧的组成

c.出错帧

出错帧由出错叠加标志和错误界定符组成。出错叠加标志包括了多个出错信息的标志。

d.超载帧

超载帧包括超载标志和超载界定符。超载发生在两种情况下:一个是接收器因内部条件要求推迟下一个数据帧或者远程帧的发送;另一个是在间歇字段检测到显性值时。

e.帧间空间

数据帧或远程帧通过帧间空间与前一帧隔开,而不管前一帧是何种类型的帧。而在超载帧与出错帧前面不需要帧间空间,多个超载帧之间也不需要帧间空间来作分隔。

1.4 位时序(Bit Timing)

CAN 总线协议规定,报文传输的同步或者非同步方式的选择通过位时序来实现。CAN 总线中位时序包括正常位速率和正常位时间两个参数。

• 正常位速率(Nominal Bit Rate):在非重同步情况下,借助理想发送器每秒发送的位数。

• 正常位时间(Nominal Bit Time):正常位速率的倒数。正常位时间由几个不同的时间段组成,它们是同步段(SYNC_SEG)、传播段(PROP_SEG)、相位缓冲段 1(PHASE_SEG1)、相位缓冲段 2(PHASE_SEG2),如图 7 所示。

图片[7]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 7 正常位时间的组成

• 同步段:在这段时间内,完成总线上各个节点的同步,需要一个跳变沿。

• 传播段:这个时间段是指网络上传输的延迟时间,它是信号在总线上传播时间、输入比较器延迟和输出驱动器延迟之和的两倍。

• 相位缓冲段 1 和相位缓冲段 2:它们用于弥补跳变沿的相位误差造成的影响。通过重同步,这两个时间段可以被延长或缩短。

• 采样点:这是读取总线电平并理解该位数值的时刻,它位于相位缓冲段 1 的终点。

1.5 同步(Synchronization)

a.硬同步(Hard Synchronization)

硬同步后,内部位时间从同步段(SYNC_SEG)重新开始,它迫使触发该硬同步的跳变沿处于新的位时间的同步段(SYNC_SEG)之内。

b.重同步(Resynchronization)

当引起重同步沿的相位误差小于或等于重同步跳转宽度编程值时,重同步的作用和硬同步相同。若相位误差大于重同步跳转宽度且相位误差为正时,则相位缓冲段 1(PHASE_SEG1)延长总数为重同步跳转宽度。若相位误差大于重同步跳转宽度且相位误差为负时,则相位缓冲段2(PHASE_SEG2)缩短总数为重同步跳转宽度。

c.重同步跳转宽度(Resynchronization Jump Width)

由于重同步的结果,PHASE_SEG1 可被延长或 PHASE_SEG2 可被缩短。相位缓冲段长度的改变量不应大于重同步跳转宽度。

d.同步的规则CAN 

通信协议规定,同步包括硬同步和重同步两种形式。它们遵从下列几条规则:

  • 在一个位时间内仅允许一种同步。
  • 对于一个跳变沿,仅当它前面的第一个采样点数值与紧跟该跳变沿之后的总线值不相同时,才把该跳变沿用于同步。
  • 在总线空闲期间,若出现一个从隐性值到显性值的跳变沿,则执行一次硬同步。
  • 符合规则前两条规则的从隐性值到显性值的跳变沿都被用于重同步(在低比特率时也可选择从显性值到隐性值的跳变沿),例外的情况是具有正相位误差的隐性值到显性值的跳变沿将不会导致重同步。

二、CAN 通信控制器程序基本框架

CAN 总线的通信协议由 CAN 通信控制器完成。CAN 通信控制器由实现 CAN 总线协议部分和微控制器部分的电路组成。下面将通过一个实例讲解如何用 FPGA 实现 CAN 通信控制器的功能。这个实例从功能和结构上完全参照 SJA 1000 CAN 通信控制器。

2.1 SJA1000CAN 通信控制器

SJA1000 是 Philips 公司于 1997 年推出的一种独立 CAN 总线控制器。它实现了 CAN 总线物理层和数据链路层的所有功能。SJA 1000 通信控制器的功能框图如图 8 所示。

SJA 1000 主要由以下几部分构成:

• 接口管理逻辑 处理来自主 CPU 的命令,控制 CAN 寄存器的寻址,并为主 CPU 提供中断和状态信息。

• 发送缓冲器 它是 CPU 和位数据流处理器(BSP)之间的接口,能存储一条可发送到 CAN总线上的完整报文。报文由 CPU 写入,由位数据流处理器读出。

图片[8]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 8 SJA1000 通信控制器功能框图

• 接收缓冲器 它是接收 FIFO 的一个可被 CPU 访问的窗口。在接收 FIFO 的支持下,CPU可以在处理当前信息的同时接收总线上的其他信息。

• 接收滤波器 它把收到的报文标识符和接收滤波器寄存器中的内容进行比较,以判断该报文是否应该被接收。如果符合接收条件,则报文被存入接收 FIFO 中。

• 位数据流处理器 它是一个序列发生器,控制发送缓冲器、接收 FIFO 和 CAN 总线之间的数据流,同时它也执行错误检测、仲裁、位填充和 CAN 总线错误处理功能。

• 位时序逻辑 它监视串行 CAN 总线并处理与总线相关的位时序。它在报文开始发送,总线电平从隐性值跳变到显性值时同步于 CAN 总线上的位数据流(硬同步),并在该报文的传送过程中,每遇到一次从隐性值到显性值的跳变沿就进行一次重同步(软同步)。位时序逻辑还提供可编程的时间段来补偿传播延迟时间和相位漂移(如晶振导致的漂移),还能定义采样点以及每一个位时间内的采样次数。

• 错误管理逻辑 它按照 CAN 协议完成传输错误界定。它接收来自位数据流处理器 BSP 的出错通知,并向位数据流处理器 BSP 和接口管理逻辑提供出错统计。

2.2 CAN 通信控制器程序框架

实现的 CAN 通信控制器参照 SJA1000 CAN 通信控制器的结构,程序基本框架如图 9 所示。

图片[9]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 9 CAN 通信控制器结构框图

三、CAN 通信控制器的具体实现

各模块的组织结构如图 10 所示。

图片[10]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 10 程序组织结构

3.1 顶层控制程序——TOP

TOP 程序处于整个程序的最顶层,控制其他部分的正常运行。主要程序代码如下:

//连接其他模块
//寄存器模块
  can_registers i_can_registers (
                                .clk(clk_i),
                                .rst(rst),
                                .cs(cs),
                                .we(we),
                              ….)
                            
//连接 Bit Timing Logic 模块
  can_btl i_can_btl (
                      .clk(clk_i),
                      .rst(rst),
                      .rx(rx_i),
                     …)
                   
//连接 Bit Streaming Processor 模块
  can_bsp i_can_bsp(
                      .clk(clk_i),
                      .rst(rst),
                    …)


//选择输出 fifo 或者寄存器中的数据模式
  always @ (extended_mode or addr or reset_mode)
    begin
      if (extended_mode & (~reset_mode) & ((addr >= 8'd16) && (addr <= 8'd28)) | (~extended_mode)
        & ((addr >= 8'd20) && (addr <= 8'd29)))
        data_out_fifo_selected <= 1'b1;
      else
        data_out_fifo_selected <= 1'b0;
      end
      
 //输出数据
    always @ (posedge clk_i)
      begin
        if (cs & (~we))
          begin
            if (data_out_fifo_selected)
              data_out <=#Tp data_out_fifo;
        else
            data_out <=#Tp data_out_regs;
          end
      end


// 锁存地址
  always @ (negedge clk_i or posedge rst)
    begin
      if (rst)
        addr_latched <= 8'h0;
      else if (ale_i)
        addr_latched <=#Tp port_0_io;
    end
    
// 产生延迟信号
  always @ (posedge clk_i or posedge rst)
    begin
      if (rst)
        begin
          wr_i_q <= 1'b0;
          rd_i_q <= 1'b0;
        end
      else
        begin
          wr_i_q <=#Tp wr_i;
          rd_i_q <=#Tp rd_i;
        end
    end
  
//组合得到多个信号,如片选、重起等
  assign cs = ((wr_i & (~wr_i_q)) | (rd_i & (~rd_i_q))) & cs_can_i;
  assign rst = rst_i;
  assign we = wr_i;
  assign addr = addr_latched;
  assign data_in = port_0_io;
  assign port_0_io = (cs_can_i & rd_i)? data_out : 8'hz;

3.2 寄存器控制

这个模块用于完成程序中所有有关寄存器的操作,代码如下:

 

3.3 位时序逻辑——Bit Timing Logic

位时序逻辑实现 CAN 总线协议中对位同步的有关控制。位时序逻辑监视串行 CAN 总线并处理与总线相关的位时序。它在报文开始发送、总线电平从隐性值跳变到显性值时同步于 CAN总线上的位数据流(硬同步),并在该报文的传送过程中,每遇到一次从隐性值到显性值的跳变沿就进行一次重同步(软同步)。位时序逻辑还提供可编程的时间段来补偿传播延迟时间和相位漂移。主要程序代码如下:

 

3.4 位数据流处理器——Bit Stream Processor

位数据流处理器负责完成程序中所有有关数据的操作。位数据流处理器实际上就是一个序列发生器,它控制发送缓冲器、接收 FIFO 和 CAN 总线之间的数据流,同时它也执行错误检测、仲裁、位填充和 CAN 总线错误处理功能。位数据流处理器程序结构如图 11 所示。

图片[11]-基于FPGA的CAN总线控制器的设计(附代码)-FPGA CPLD资料源码分享社区-FPGA CPLD-ChipDebug

图 11 位数据流处理器程序结构

主要程序代码如下:

 

3.5 CRC 校验

CAN 节点中设有错误检测、标定和自检等措施。检测错误包括多种方式,其中最常用、最有效的一种是 CRC 校验。CRC 序列由循环冗余校验码求得的帧检查序组成。为实现 CRC 计算,被除的多项式系数由包括帧起始、仲裁字段、控制字段、数据字段在内的无填充位数据流给出,其 15 个最低位的系数为 0。

此多项式被发生器产生的下列多项式除(系数为模 2 运算):1 3478101415XXXXXXX +++++++该多项式除法的余数即为发向总线的 CRC 序列。为完成此运算,可以使用一个 15 位的移位寄存器 CRC-RG(14:0)。被除多项式位数据流由帧起始到数据字段结束的无填充序列给定,如果以 NXTBIT 标记该位数据流的下一位,则 CRC 序列可以用如下的方式求得:

 

完成数据 CRC 校验的主要代码如下:

 

3.6 FIFO

为实现数据的快速交换,使用了 FIFO,代码如下:

 


四、程序的仿真与测试

CAN 总线通信控制器的仿真程序,需要模拟数据的发送和接收。

下面是测试程序的部分代码:

//连接 can_top 模块
  can_top i_can_top(
                    .cs_can_i(cs_can),
                    .clk_i(clk),
                    .rx_i(rx_and_tx),
                    .tx_o(tx),
                    .irq_on(irq),
                    .clkout_o(clkout)
                   );


//产生 24 MHz 时钟
  initial
  begin
    clk=0;
    forever #21 clk = ~clk;
  end


//初始化
  initial
  begin
    start_tb = 0;
    cs_can = 0;
    rx = 1;
    extended_mode = 0;
    tx_bypassed = 0;
    rst_i = 1'b0;
    ale_i = 1'b0;
    rd_i = 1'b0;
    wr_i = 1'b0;
    port_0_o = 8'h0;
    port_0_en = 0;
    port_free = 1;
    rst_i = 1;
    #200 rst_i = 0;
    #200 start_tb = 1;
  end


//产生延迟的 tx 信号(CAN 发送器延迟)
  always
  begin
    wait (tx);
    repeat (4*BRP) @ (posedge clk); // 4 time quants delay
    #1 delayed_tx = tx;
    wait (~tx);
    repeat (4*BRP) @ (posedge clk); // 4 time quants delay
    #1 delayed_tx = tx;
  end
  
  assign rx_and_tx = rx & (delayed_tx | tx_bypassed); // When this signal is on, tx is not
  looped back to the rx.
  
//主程序
  initial
  begin
    wait(start_tb);


//设置总线时序寄存器
    write_register(8'd6, {`CAN_TIMING0_SJW, `CAN_TIMING0_BRP});
    write_register(8'd7, {`CAN_TIMING1_SAM, `CAN_TIMING1_TSEG2, `CAN_TIMING1_TSEG1});


// 设置时钟分频寄存器
    extended_mode = 1'b0;
    write_register(8'd31, {extended_mode, 3'h0, 1'b0, 3'h0}); // Setting the normal mode (not
    extended)


//设置接收代码和接收寄存器
    write_register(8'd16, 8'ha6); // acceptance code 0
    write_register(8'd17, 8'hb0); // acceptance code 1
    write_register(8'd18, 8'h12); // acceptance code 2
    write_register(8'd19, 8'h30); // acceptance code 3
    write_register(8'd20, 8'h0); // acceptance mask 0
    write_register(8'd21, 8'h0); // acceptance mask 1
    write_register(8'd22, 8'h00); // acceptance mask 2
    write_register(8'd23, 8'h00); // acceptance mask 3
    write_register(8'd4, 8'he8); // acceptance code
    write_register(8'd5, 8'h0f); // acceptance mask
    #10;
    repeat (1000) @ (posedge clk);
  
//开关复位模式
    write_register(8'd0, {7'h0, ~(`CAN_MODE_RESET)});
    repeat (BRP) @ (posedge clk);
  
// 在复位后设置总线空闲
    repeat (11) send_bit(1);
    test_full_fifo; // test currently switched on
    send_frame; // test currently switched off
    bus_off_test; // test currently switched off
    forced_bus_off; // test currently switched off
    send_frame_basic; // test currently switched off
    send_frame_extended; // test currently switched off
    self_reception_request; // test currently switched off
    manual_frame_basic; // test currently switched off
    manual_frame_ext; // test currently switched off
    $display("CAN Testbench finished !");
  $stop;
  end

在测试过程中通过多个任务来分别验证程序的各个功能模块。下面的程序用于验证强制关闭总线任务:

下面的程序验证如何发送一个基本格式的帧数据:

 

五、总结

本篇通过一个实例讲解如何用 FPGA 实现 CAN 总线通信控制器。首先讲解了 CAN 总线协议的有关内容,然后介绍了一种常用的 CAN 通信控制器 SJA1000 的主要特点。接下来讲解程序的主要框架和具体代码。最后通过一个测试程序验证了程序。这个实例为读者实现自己的 CAN总线通信控制器提供了一个可以应用的案例。

 

请登录后发表评论