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

原创 FPGA技术江湖 2024-11-25 10:56

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。


今天给大侠带来基于FPGA的CAN总线控制器的设计,包括CAN 总线协议解析以及 CAN 通信控制器程序基本框架、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 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 非归零编码方式

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 数据帧的具体组成


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

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

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

图 4 仲裁字段的组成

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

图 5 控制字段的组成


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


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


b.远程帧

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

图 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 正常位时间的组成


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

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

• 相位缓冲段 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 SJA1000 通信控制器功能框图


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

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

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

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

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


2.2 CAN 通信控制器程序框架

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

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

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


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

图 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 寄存器控制

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

  always @ (posedge clk)    begin      tx_successful_q <=#Tp tx_successful;      overrun_q <=#Tp overrun;      transmit_buffer_status_q <=#Tp transmit_buffer_status;      info_empty_q <=#Tp info_empty;      error_status_q <=#Tp error_status;      node_bus_off_q <=#Tp node_bus_off;      node_error_passive_q <=#Tp node_error_passive;    end        //模式寄存器  wire [0:0] mode;  wire [4:1] mode_basic;  wire [3:1] mode_ext;  wire receive_irq_en_basic;  wire transmit_irq_en_basic;  wire error_irq_en_basic;  wire overrun_irq_en_basic;    can_register_asyn_syn #(1, 1'h1) MODE_REG0(                                               .data_in(data_in[0]),                                              .data_out(mode[0]),                                              .we(we_mode),                                              .clk(clk),                                              .rst(rst),                                              .rst_sync(set_reset_mode)                                             );                                               can_register_asyn #(4, 0) MODE_REG_BASIC(                                             .data_in(data_in[4:1]),                                            .data_out(mode_basic[4:1]),                                            .we(we_mode),                                            .clk(clk),                                            .rst(rst)                                           );                                             can_register_asyn #(3, 0) MODE_REG_EXT(                                           .data_in(data_in[3:1]),                                          .data_out(mode_ext[3:1]),                                          .we(we_mode & reset_mode),                                          .clk(clk),                                          .rst(rst)                                         );  //命令寄存器  wire [4:0] command;    can_register_asyn_syn #(1, 1'h0) COMMAND_REG0(                                                 .data_in(data_in[0]),                                                .data_out(command[0]),                                                .we(we_command),                                                .clk(clk),                                                .rst(rst),                                                .rst_sync(tx_request & sample_point)                                              );    can_register_asyn_syn #(1, 1'h0) COMMAND_REG1(                                                 .data_in(data_in[1]),                                                .data_out(command[1]),                                                .we(we_command),                                                .clk(clk),                                                .rst(rst),                                                .rst_sync(abort_tx & ~transmitting)                                              );    can_register_asyn_syn #(2, 2'h0) COMMAND_REG(                                                 .data_in(data_in[3:2]),                                                .data_out(command[3:2]),                                                .we(we_command),                                                .clk(clk),                                                .rst(rst),                                                .rst_sync(|command[3:2])                                              );
  can_register_asyn_syn #(1, 1'h0) COMMAND_REG4(  .data_in(data_in[4]), .data_out(command[4]), .we(we_command), .clk(clk), .rst(rst), .rst_sync(tx_successful & (~tx_successful_q) | abort_tx) ); assign self_rx_request = command[4] & (~command[0]); assign clear_data_overrun = command[3]; assign release_buffer = command[2]; assign abort_tx = command[1] & (~command[0]) & (~command[4]); assign tx_request = command[0] | command[4]; always @ (posedge clk or posedge rst) begin if (rst) single_shot_transmission <= 1'b0; else if (we_command & data_in[1] & (data_in[1] | data_in[4])) single_shot_transmission <=#Tp 1'b1; else if (tx_successful & (~tx_successful_q)) single_shot_transmission <=#Tp 1'b0; end //状态寄存器 wire [7:0] status; assign status[7] = node_bus_off; assign status[6] = error_status; assign status[5] = transmit_status; assign status[4] = receive_status; assign status[3] = transmission_complete; assign status[2] = transmit_buffer_status; assign status[1] = overrun_status; assign status[0] = receive_buffer_status; always @ (posedge clk or posedge rst) begin if (rst) transmission_complete <= 1'b1; else if (tx_successful & (~tx_successful_q) | abort_tx) transmission_complete <=#Tp 1'b1; else if (tx_request) transmission_complete <=#Tp 1'b0; end always @ (posedge clk or posedge rst) begin if (rst) transmit_buffer_status <= 1'b1; else if (tx_request) transmit_buffer_status <=#Tp 1'b0; else if (~need_to_tx) transmit_buffer_status <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin    if (rst) overrun_status <= 1'b0; else if (overrun & (~overrun_q)) overrun_status <=#Tp 1'b1; else if (clear_data_overrun) overrun_status <=#Tp 1'b0; end always @ (posedge clk or posedge rst) begin if (rst) receive_buffer_status <= 1'b0; else if (release_buffer) receive_buffer_status <=#Tp 1'b0; else if (~info_empty) receive_buffer_status <=#Tp 1'b1; end //总线时序寄存器 1 wire [7:0] bus_timing_0;   can_register #(8) BUS_TIMING_0_REG(  .data_in(data_in), .data_out(bus_timing_0), .we(we_bus_timing_0), .clk(clk)                                    );                                     assign baud_r_presc = bus_timing_0[5:0]; assign sync_jump_width = bus_timing_0[7:6]; //总线时序寄存器 2 wire [7:0] bus_timing_1;   can_register #(8) BUS_TIMING_1_REG(  .data_in(data_in), .data_out(bus_timing_1), .we(we_bus_timing_1), .clk(clk)                                     );                                      assign time_segment1 = bus_timing_1[3:0]; assign time_segment2 = bus_timing_1[6:4]; assign triple_sampling = bus_timing_1[7]; //错误提示寄存器  can_register_asyn #(8, 96) ERROR_WARNING_REG(  .data_in(data_in), .data_out(error_warning_limit), .we(we_error_warning_limit), .clk(clk), .rst(rst)                                               );                                               //时钟分频寄存器 wire [7:0] clock_divider; wire clock_off; wire [2:0] cd; reg [2:0] clkout_div; reg [2:0] clkout_cnt; reg clkout_tmp; //reg clkout;    can_register #(1) CLOCK_DIVIDER_REG_7(  .data_in(data_in[7]), .data_out(clock_divider[7]), .we(we_clock_divider_hi), .clk(clk)                                       ); assign clock_divider[6:4] = 3'h0;   can_register #(1) CLOCK_DIVIDER_REG_3(  .data_in(data_in[3]), .data_out(clock_divider[3]), .we(we_clock_divider_hi), .clk(clk)                                       );                                         can_register #(3) CLOCK_DIVIDER_REG_LOW(  .data_in(data_in[2:0]), .data_out(clock_divider[2:0]), .we(we_clock_divider_low), .clk(clk) ); assign extended_mode = clock_divider[7]; assign clock_off = clock_divider[3]; assign cd[2:0] = clock_divider[2:0]; always @ (cd) begin case (cd) // synopsys_full_case synopsys_paralel_case 3'b000 : clkout_div <= 0; 3'b001 : clkout_div <= 1; 3'b010 : clkout_div <= 2; 3'b011 : clkout_div <= 3; 3'b100 : clkout_div <= 4; 3'b101 : clkout_div <= 5; 3'b110 : clkout_div <= 6; 3'b111 : clkout_div <= 0; endcase end always @ (posedge clk or posedge rst) begin if (rst) clkout_cnt <= 3'h0; else if (clkout_cnt == clkout_div) clkout_cnt <=#Tp 3'h0; else clkout_cnt <= clkout_cnt + 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) clkout_tmp <= 1'b0; else if (clkout_cnt == clkout_div) clkout_tmp <=#Tp ~clkout_tmp; end always @ (cd or clkout_tmp or clock_off) begin if (clock_off) clkout <=#Tp 1'b1; else clkout <=#Tp clkout_tmp; end assign clkout = clock_off ? 1'b1 : ((&cd)? clk : clkout_tmp); //从寄存器中读数据 always @ ( addr or read or extended_mode or mode or bus_timing_0 or bus_timing_1 or clock_divider or acceptance_code_0 or acceptance_code_1 or acceptance_code_2 or acceptance_code_3 or acceptance_mask_0 or acceptance_mask_1 or acceptance_mask_2 or acceptance_mask_3 or reset_mode or tx_data_0 or tx_data_1 or tx_data_2 or tx_data_3 or tx_data_4 or tx_data_5 or tx_data_6 or tx_data_7 or tx_data_8 or tx_data_9 or status or error_warning_limit or rx_err_cnt or tx_err_cnt or irq_en_ext or irq_reg or mode_ext or arbitration_lost_capture or rx_message_counter or mode_basic or error_capture_code  ) begin if(read) // read begin if (extended_mode) // EXTENDED mode (Different register map depends on mode) begin case(addr) 8'd0 : data_out_tmp <= {4'b0000, mode_ext[3:1], mode[0]}; 8'd1 : data_out_tmp <= 8'h0; 8'd2 : data_out_tmp <= status; 8'd3 : data_out_tmp <= irq_reg; 8'd4 : data_out_tmp <= irq_en_ext; 8'd6 : data_out_tmp <= bus_timing_0; 8'd7 : data_out_tmp <= bus_timing_1; 8'd11 : data_out_tmp <= {3'h0, arbitration_lost_capture[4:0]}; 8'd12 : data_out_tmp <= error_capture_code; 8'd13 : data_out_tmp <= error_warning_limit; 8'd14 : data_out_tmp <= rx_err_cnt; 8'd15 : data_out_tmp <= tx_err_cnt; 8'd16 : data_out_tmp <= acceptance_code_0; 8'd17 : data_out_tmp <= acceptance_code_1; 8'd18 : data_out_tmp <= acceptance_code_2; 8'd19 : data_out_tmp <= acceptance_code_3; 8'd20 : data_out_tmp <= acceptance_mask_0; 8'd21 : data_out_tmp <= acceptance_mask_1; 8'd22 : data_out_tmp <= acceptance_mask_2; 8'd23 : data_out_tmp <= acceptance_mask_3; 8'd24 : data_out_tmp <= 8'h0; 8'd25 : data_out_tmp <= 8'h0; 8'd26 : data_out_tmp <= 8'h0; 8'd27 : data_out_tmp <= 8'h0; 8'd28 : data_out_tmp <= 8'h0; 8'd29 : data_out_tmp <= {1'b0, rx_message_counter}; 8'd31 : data_out_tmp <= clock_divider; default: data_out_tmp <= 8'h0; endcase end else // BASIC mode begin case(addr) 8'd0 : data_out_tmp <= {3'b001, mode_basic[4:1], mode[0]}; 8'd1 : data_out_tmp <= 8'hff; 8'd2 : data_out_tmp <= status; 8'd3 : data_out_tmp <= {4'hf, irq_reg[3:0]}; 8'd4 : data_out_tmp <= reset_mode? acceptance_code_0 : 8'hff; 8'd5 : data_out_tmp <= reset_mode? acceptance_mask_0 : 8'hff; 8'd6 : data_out_tmp <= reset_mode? bus_timing_0 : 8'hff; 8'd7 : data_out_tmp <= reset_mode? bus_timing_1 : 8'hff; 8'd10 : data_out_tmp <= reset_mode? 8'hff : tx_data_0; 8'd11 : data_out_tmp <= reset_mode? 8'hff : tx_data_1; 8'd12 : data_out_tmp <= reset_mode? 8'hff : tx_data_2; 8'd13 : data_out_tmp <= reset_mode? 8'hff : tx_data_3; 8'd14 : data_out_tmp <= reset_mode? 8'hff : tx_data_4; 8'd15 : data_out_tmp <= reset_mode? 8'hff : tx_data_5; 8'd16 : data_out_tmp <= reset_mode? 8'hff : tx_data_6; 8'd17 : data_out_tmp <= reset_mode? 8'hff : tx_data_7; 8'd18 : data_out_tmp <= reset_mode? 8'hff : tx_data_8; 8'd19 : data_out_tmp <= reset_mode? 8'hff : tx_data_9; 8'd31 : data_out_tmp <= clock_divider; default: data_out_tmp <= 8'h0; endcase end end else data_out_tmp <= 8'h0; end always @ (posedge clk or posedge rst) begin if (rst) data_out <= 0; else if (read) data_out <=#Tp data_out_tmp; end




3.3 位时序逻辑——Bit Timing Logic

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

//计数器  always @ (posedge clk or posedge rst)  begin    if (rst)      clk_cnt <= 0;    else if (clk_cnt == (preset_cnt-1))      clk_cnt <=#Tp 0;    else      clk_cnt <=#Tp clk_cnt + 1;  end  //产生定义波特率的一般使能信号  always @ (posedge clk or posedge rst)  begin    if (rst)      clk_en <= 1'b0;    else if (clk_cnt == (preset_cnt-1))      clk_en <=#Tp 1'b1;    else      clk_en <=#Tp 1'b0;  end  //改变状态  assign go_sync = clk_en & (seg2 & (~hard_sync) & (~resync) & ((quant_cnt == time_segment2)));  assign go_seg1 = clk_en & (sync | hard_sync | (resync & seg2 & sync_window) | (resync_latched  & sync_window));  assign go_seg2 = clk_en & (seg1 & (~hard_sync) & (quant_cnt == (time_segment1 + delay)));  //当探测到 SJW 字段的沿时,同步请求被锁存并被执行  always @ (posedge clk or posedge rst)  begin    if (rst)      resync_latched <= 1'b0;    else if (resync & seg2 & (~sync_window))      resync_latched <=#Tp 1'b1;    else if (go_seg1)      resync_latched <= 1'b0;  end
//同步的平台或片断 always @ (posedge clk or posedge rst) begin if (rst) sync <= 0; else if (go_sync) sync <=#Tp 1'b1; else if (go_seg1) sync <=#Tp 1'b0; end assign tx_point = go_sync; //片断 seg1 always @ (posedge clk or posedge rst) begin if (rst) seg1 <= 1; else if (go_seg1) seg1 <=#Tp 1'b1; else if (go_seg2) seg1 <=#Tp 1'b0; end //片断 seg2 always @ (posedge clk or posedge rst) begin if (rst) seg2 <= 0; else if (go_seg2) seg2 <=#Tp 1'b1; else if (go_sync | go_seg1) seg2 <=#Tp 1'b0; end //Quant 计数器 always @ (posedge clk or posedge rst) begin if (rst) quant_cnt <= 0; else if (go_sync | go_seg1 | go_seg2) quant_cnt <=#Tp 0; else if (clk_en) quant_cnt <=#Tp quant_cnt + 1'b1; end //当探测到后沿时,片断 seg1 被延时 begin if (rst) delay <= 0; else if (clk_en & resync & seg1) delay <=#Tp (quant_cnt > sync_jump_width)? (sync_jump_width + 1) : (quant_cnt + 1); else if (go_sync | go_seg1) delay <=#Tp 0; end //如果沿出现在这个窗口中,相位的错误将得到完全的补偿 assign sync_window = ((time_segment2 - quant_cnt) < ( sync_jump_width + 1));
//数据采样 always @ (posedge clk or posedge rst) begin if (rst) sample <= 2'b11; else if (clk_en) sample <= {sample[0], rx}; end
//获得使能后,采样完成 always @ (posedge clk or posedge rst) begin if (rst) begin sampled_bit <= 1; sampled_bit_q <= 1; sample_point <= 0; end else if (clk_en & (~hard_sync)) begin if (seg1 & (quant_cnt == (time_segment1 + delay))) begin sample_point <=#Tp 1; sampled_bit_q <=#Tp sampled_bit; if (triple_sampling) sampled_bit <=#Tp (sample[0] & sample[1]) | ( sample[0] & rx) | (sample[1] & rx); else sampled_bit <=#Tp rx; end end else sample_point <=#Tp 0; end //阻塞同步 always @ (posedge clk or posedge rst) begin if (rst) sync_blocked <=#Tp 1'b0; else if (clk_en) begin if (hard_sync | resync) sync_blocked <=#Tp 1'b1; else if (seg2 & quant_cnt == time_segment2) sync_blocked <=#Tp 1'b0; end end
//阻塞重同步直到收到开始信号 /* Blocking resynchronization until reception starts (needed because after reset mode exits we are waiting for end-of-frame and interframe. No resynchronization is needed meanwhile). */ always @ (posedge clk or posedge rst) begin if (rst) resync_blocked <=#Tp 1'b1; else if (reset_mode) resync_blocked <=#Tp 1'b1; else if (hard_sync) resync_blocked <=#Tp 1'b0; end



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

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

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


主要程序代码如下:

//各个数据收发的起始状态//接收数据的 idle 状态  always @ (posedge clk or posedge rst)  begin    if (rst)      rx_idle <= 1'b0;    else if (reset_mode | go_rx_id1 | error_frame)      rx_idle <=#Tp 1'b0;    else if (go_rx_idle)      rx_idle <=#Tp 1'b1;  end
// 接收数据的 id1 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_id1 <= 1'b0; else if (reset_mode | go_rx_rtr1 | error_frame) rx_id1 <=#Tp 1'b0; else if (go_rx_id1) rx_id1 <=#Tp 1'b1; end
//接收数据的 rtr1 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_rtr1 <= 1'b0; else if (reset_mode | go_rx_ide | error_frame) rx_rtr1 <=#Tp 1'b0; else if (go_rx_rtr1) rx_rtr1 <=#Tp 1'b1; end
//接收数据的 ide 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_ide <= 1'b0; else if (reset_mode | go_rx_r0 | go_rx_id2 | error_frame) rx_ide <=#Tp 1'b0; else if (go_rx_ide) rx_ide <=#Tp 1'b1; end //接收数据的 id2 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_id2 <= 1'b0; else if (reset_mode | go_rx_rtr2 | error_frame) rx_id2 <=#Tp 1'b0; else if (go_rx_id2) rx_id2 <=#Tp 1'b1; end //接收数据的 rtr2 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_rtr2 <= 1'b0; else if (reset_mode | go_rx_r1 | error_frame) rx_rtr2 <=#Tp 1'b0; else if (go_rx_rtr2) rx_rtr2 <=#Tp 1'b1; end
//接收数据的 r0 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_r1 <= 1'b0; else if (reset_mode | go_rx_r0 | error_frame) rx_r1 <=#Tp 1'b0; else if (go_rx_r1) rx_r1 <=#Tp 1'b1; end //接收数据的 r0 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_r0 <= 1'b0; else if (reset_mode | go_rx_dlc | error_frame) rx_r0 <=#Tp 1'b0; else if (go_rx_r0) rx_r0 <=#Tp 1'b1; end
//接收数据的 dlc 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_dlc <= 1'b0; else if (reset_mode | go_rx_data | go_rx_crc | error_frame) rx_dlc <=#Tp 1'b0; else if (go_rx_dlc) rx_dlc <=#Tp 1'b1; end //接收数据状态 always @ (posedge clk or posedge rst) begin if (rst) rx_data <= 1'b0; else if (reset_mode | go_rx_crc | error_frame) rx_data <=#Tp 1'b0; else if (go_rx_data) rx_data <=#Tp 1'b1; end // 接收数据的 crc 状态 always @ (posedge clk or posedge rst) begin if (rst) rx_crc <= 1'b0; else if (reset_mode | go_rx_crc_lim | error_frame) rx_crc <=#Tp 1'b0; else if (go_rx_crc) rx_crc <=#Tp 1'b1; end
//接收数据 crc 分隔符状态 always @ (posedge clk or posedge rst) begin if (rst) rx_crc_lim <= 1'b0; else if (reset_mode | go_rx_ack | error_frame) rx_crc_lim <=#Tp 1'b0; else if (go_rx_crc_lim) rx_crc_lim <=#Tp 1'b1; end
//接收数据的应答状态 always @ (posedge clk or posedge rst) begin if (rst) rx_ack <= 1'b0; else if (reset_mode | go_rx_ack_lim | error_frame) rx_ack <=#Tp 1'b0; else if (go_rx_ack) rx_ack <=#Tp 1'b1; end //接收数据分隔符状态 always @ (posedge clk or posedge rst) begin if (rst) rx_ack_lim <= 1'b0; else if (reset_mode | go_rx_eof | error_frame) rx_ack_lim <=#Tp 1'b0; else if (go_rx_ack_lim) rx_ack_lim <=#Tp 1'b1; end
//接收数据的帧尾状态 always @ (posedge clk or posedge rst) begin if (rst) rx_eof <= 1'b0; else if (go_rx_inter | error_frame | go_overload_frame) rx_eof <=#Tp 1'b0; else if (go_rx_eof) rx_eof <=#Tp 1'b1; end //帧间空间状态 always @ (posedge clk or posedge rst) begin if (rst) rx_inter <= 1'b0; else if (reset_mode | go_rx_idle | go_rx_id1 | go_overload_frame | go_error_frame) rx_inter <=#Tp 1'b0; else if (go_rx_inter) rx_inter <=#Tp 1'b1; end // ID 寄存器 always @ (posedge clk or posedge rst) begin if (rst) id <= 0; else if (sample_point & (rx_id1 | rx_id2) & (~bit_de_stuff)) id <=#Tp {id[27:0], sampled_bit}; end // rtr1 位 always @ (posedge clk or posedge rst) begin if (rst) rtr1 <= 0; else if (sample_point & rx_rtr1 & (~bit_de_stuff)) rtr1 <=#Tp sampled_bit; end // rtr2 位 always @ (posedge clk or posedge rst) begin if (rst) rtr2 <= 0; else if (sample_point & rx_rtr2 & (~bit_de_stuff)) rtr2 <=#Tp sampled_bit; end
// ide 位 always @ (posedge clk or posedge rst) begin if (rst) ide <= 0; else if (sample_point & rx_ide & (~bit_de_stuff)) ide <=#Tp sampled_bit; end
// 获得数据长度 always @ (posedge clk or posedge rst) begin if (rst) data_len <= 0; else if (sample_point & rx_dlc & (~bit_de_stuff)) data_len <=#Tp {data_len[2:0], sampled_bit}; end // 获得数据 always @ (posedge clk or posedge rst) begin if (rst) tmp_data <= 0; else if (sample_point & rx_data & (~bit_de_stuff)) tmp_data <=#Tp {tmp_data[6:0], sampled_bit}; end always @ (posedge clk or posedge rst) begin if (rst) write_data_to_tmp_fifo <= 0; else if (sample_point & rx_data & (~bit_de_stuff) & (&bit_cnt[2:0])) write_data_to_tmp_fifo <=#Tp 1'b1; else write_data_to_tmp_fifo <=#Tp 0; end always @ (posedge clk or posedge rst) begin if (rst) byte_cnt <= 0; else if (write_data_to_tmp_fifo) byte_cnt <=#Tp byte_cnt + 1; else if (reset_mode | (sample_point & go_rx_crc_lim)) byte_cnt <=#Tp 0; end always @ (posedge clk) begin if (write_data_to_tmp_fifo) tmp_fifo[byte_cnt] <=#Tp tmp_data;  end// CRC 校验数据 always @ (posedge clk or posedge rst) begin if (rst) crc_in <= 0; else if (sample_point & rx_crc & (~bit_de_stuff)) crc_in <=#Tp {crc_in[13:0], sampled_bit}; end //计数器 always @ (posedge clk or posedge rst) begin if (rst) bit_cnt <= 0; else if (go_rx_id1 | go_rx_id2 | go_rx_dlc | go_rx_data | go_rx_crc | go_rx_ack | go_rx_eof | go_rx_inter | go_error_frame | go_overload_frame) bit_cnt <=#Tp 0; else if (sample_point & (~bit_de_stuff)) bit_cnt <=#Tp bit_cnt + 1'b1; end //帧尾计数器 always @ (posedge clk or posedge rst) begin if (rst) eof_cnt <= 0; else if (sample_point) begin if (reset_mode | go_rx_inter | go_error_frame | go_overload_frame) eof_cnt <=#Tp 0; else if (rx_eof) eof_cnt <=#Tp eof_cnt + 1'b1; end end // 使能位填充 always @ (posedge clk or posedge rst) begin if (rst) bit_stuff_cnt_en <= 1'b0; else if (bit_de_stuff_set) bit_stuff_cnt_en <=#Tp 1'b1; else if (bit_de_stuff_reset) bit_stuff_cnt_en <=#Tp 1'b0; end
//位填充计数器 always @ (posedge clk or posedge rst) begin if (rst) bit_stuff_cnt <= 1; else if (bit_de_stuff_reset) bit_stuff_cnt <=#Tp 1; else if (sample_point & bit_stuff_cnt_en) begin if (bit_stuff_cnt == 5) bit_stuff_cnt <=#Tp 1; else if (sampled_bit == sampled_bit_q) bit_stuff_cnt <=#Tp bit_stuff_cnt + 1'b1; else bit_stuff_cnt <=#Tp 1; end end // 发送数据的使能位填充 always @ (posedge clk or posedge rst) begin if (rst) bit_stuff_cnt_tx_en <= 1'b0; else if (bit_de_stuff_set & transmitting) bit_stuff_cnt_tx_en <=#Tp 1'b1; else if (bit_de_stuff_reset) bit_stuff_cnt_tx_en <=#Tp 1'b0; end
//发送数据的位填充计数 always @ (posedge clk or posedge rst) begin if (rst) bit_stuff_cnt_tx <= 1; else if (bit_de_stuff_reset) bit_stuff_cnt_tx <=#Tp 1; else if (tx_point_q & bit_stuff_cnt_en) begin if (bit_stuff_cnt_tx == 5) bit_stuff_cnt_tx <=#Tp 1; else if (tx == tx_q) bit_stuff_cnt_tx <=#Tp bit_stuff_cnt_tx + 1'b1; else bit_stuff_cnt_tx <=#Tp 1; end end assign bit_de_stuff = bit_stuff_cnt == 5; assign bit_de_stuff_tx = bit_stuff_cnt_tx == 5;
//位填充错误 assign stuff_err = sample_point & bit_stuff_cnt_en & bit_de_stuff & (sampled_bit == sampled_bit_q);
//产生延迟信号 always @ (posedge clk) begin reset_mode_q <=#Tp reset_mode; node_bus_off_q <=#Tp node_bus_off; end always @ (posedge clk or posedge rst) begin if (rst) crc_enable <= 1'b0; else if (go_crc_enable) crc_enable <=#Tp 1'b1; else if (reset_mode | rst_crc_enable) crc_enable <=#Tp 1'b0; end //CRC 校验错误 always @ (posedge clk or posedge rst) begin if (rst) crc_err <= 1'b0; else if (go_rx_ack) crc_err <=#Tp crc_in != calculated_crc; else if (reset_mode | error_frame_ended) crc_err <=#Tp 1'b0; end // 一般错误的条件 assign form_err = sample_point & ( ((~bit_de_stuff) & rx_ide & sampled_bit & (~rtr1)) | (rx_crc_lim & (~sampled_bit)) | (rx_ack_lim & (~sampled_bit)) | ((eof_cnt < 6) & rx_eof & (~sampled_bit) & (~tx_state) ) | (& rx_eof & (~sampled_bit) & tx_state)); always @ (posedge clk or posedge rst) begin if (rst) ack_err_latched <= 1'b0; else if (reset_mode | error_frame_ended | go_overload_frame) ack_err_latched <=#Tp 1'b0; else if (ack_err) ack_err_latched <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) bit_err_latched <= 1'b0; else if (reset_mode | error_frame_ended | go_overload_frame) bit_err_latched <=#Tp 1'b0; else if (bit_err) bit_err_latched <=#Tp 1'b1; end //规则 5 assign rule5 = (~node_error_passive) & bit_err & (error_frame & (error_cnt1 < 7) | overload_frame & (overload_cnt1 < 7) ); //规则 3 always @ (posedge clk or posedge rst) begin if (rst) rule3_exc1_1 <= 1'b0; else if (reset_mode | error_flag_over | rule3_exc1_2) rule3_exc1_1 <=#Tp 1'b0; else if (transmitter & node_error_passive & ack_err) rule3_exc1_1 <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) rule3_exc1_2 <= 1'b0; else if (reset_mode | error_flag_over) rule3_exc1_2 <=#Tp 1'b0; else if (rule3_exc1_1) rule3_exc1_2 <=#Tp 1'b1; else if ((error_cnt1 < 7) & sample_point & (~sampled_bit)) rule3_exc1_2 <=#Tp 1'b0; end always @ (posedge clk or posedge rst) begin if (rst) rule3_exc2 <= 1'b0; else if (reset_mode | error_flag_over) rule3_exc2 <=#Tp 1'b0; else if (transmitter & stuff_err & arbitration_field & sample_point & tx & (~sampled_bit)) rule3_exc2 <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) stuff_err_latched <= 1'b0; else if (reset_mode | error_frame_ended | go_overload_frame) stuff_err_latched <=#Tp 1'b0; else if (stuff_err) stuff_err_latched <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) form_err_latched <= 1'b0; else if (reset_mode | error_frame_ended | go_overload_frame) form_err_latched <=#Tp 1'b0; else if (form_err) form_err_latched <=#Tp 1'b1; end //接收数据的 CRC 校验can_crc i_can_crc_rx( .clk(clk), .data(sampled_bit), .enable(crc_enable & sample_point & (~bit_de_stuff)), .initialize(go_crc_enable), .crc(calculated_crc)                    );assign no_byte0 = rtr1 | (data_len<1);assign no_byte1 = rtr1 | (data_len<2);
// 接收数据 FIFO 的写使能 always @ (posedge clk or posedge rst) begin if (rst) wr_fifo <= 1'b0; else if (reset_wr_fifo) wr_fifo <=#Tp 1'b0; else if (go_rx_inter & id_ok & (~error_frame_ended) & ((~tx_state) | self_rx_request)) wr_fifo <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) header_cnt <= 0; else if (reset_wr_fifo) header_cnt <=#Tp 0; else if (wr_fifo & storing_header) header_cnt <=#Tp header_cnt + 1; end //数据计数器 always @ (posedge clk or posedge rst) begin if (rst) data_cnt <= 0; else if (reset_wr_fifo) data_cnt <=#Tp 0; else if (wr_fifo) data_cnt <=#Tp data_cnt + 1; end // 数据的合成并保存到 FIFO 中 always @ (extended_mode or ide or data_cnt or header_cnt or header_len or storing_header or id or rtr1 or rtr2 or data_len or tmp_fifo[0] or tmp_fifo[2] or tmp_fifo[4] or tmp_fifo[6] or tmp_fifo[1] or tmp_fifo[3] or tmp_fifo[5] or tmp_fifo[7]) begin if (storing_header) begin if (extended_mode) // extended mode begin if (ide) // extended format begin case (header_cnt) // synthesis parallel_case 3'h0 : data_for_fifo <= {1'b1, rtr2, 2'h0, data_len}; 3'h1 : data_for_fifo <= id[28:21]; 3'h2 : data_for_fifo <= id[20:13]; 3'h3 : data_for_fifo <= id[12:5]; 3'h4 : data_for_fifo <= {id[4:0], 3'h0}; default: data_for_fifo <= 0; endcase end else // standard format begin case (header_cnt) // synthesis parallel_case 3'h0 : data_for_fifo <= {1'b0, rtr1, 2'h0, data_len}; 3'h1 : data_for_fifo <= id[10:3]; 3'h2 : data_for_fifo <= {id[2:0], 5'h0}; default: data_for_fifo <= 0; endcase end end else // normal mode begin case (header_cnt) // synthesis parallel_case 3'h0 : data_for_fifo <= id[10:3]; 3'h1 : data_for_fifo <= {id[2:0], rtr1, data_len}; default: data_for_fifo <= 0; endcase end end else data_for_fifo <= tmp_fifo[data_cnt-header_len]; end // 传输错误帧 always @ (posedge clk or posedge rst) begin if (rst) error_frame <= 1'b0; else if (reset_mode | error_frame_ended | go_overload_frame) error_frame <=#Tp 1'b0; else if (go_error_frame) error_frame <=#Tp 1'b1; end always @ (posedge clk) begin if (sample_point) error_frame_q <=#Tp error_frame; end always @ (posedge clk or posedge rst) begin if (rst) error_cnt1 <= 1'b0; else if (reset_mode | error_frame_ended | go_error_frame | go_overload_frame) error_cnt1 <=#Tp 1'b0; else if (error_frame & tx_point & (error_cnt1 < 7)) error_cnt1 <=#Tp error_cnt1 + 1'b1; end assign error_flag_over = ((~node_error_passive) & sample_point & (error_cnt1 == 7) | node_error_passive & sample_point & (passive_cnt == 5)) & (~enable_error_cnt2); always @ (posedge clk or posedge rst) begin if (rst) error_flag_over_blocked <= 1'b0; else if (reset_mode | error_frame_ended | go_error_frame | go_overload_frame) error_flag_over_blocked <=#Tp 1'b0; else if (error_flag_over) error_flag_over_blocked <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) enable_error_cnt2 <= 1'b0; else if (reset_mode | error_frame_ended | go_error_frame | go_overload_frame) enable_error_cnt2 <=#Tp 1'b0; else if (error_frame & (error_flag_over & sampled_bit)) enable_error_cnt2 <=#Tp 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) error_cnt2 <= 0; else if (reset_mode | error_frame_ended | go_error_frame | go_overload_frame) error_cnt2 <=#Tp 0; else if (enable_error_cnt2 & tx_point) error_cnt2 <=#Tp error_cnt2 + 1'b1; end always @ (posedge clk or posedge rst) begin if (rst) delayed_dominant_cnt <= 0; else if (reset_mode | enable_error_cnt2 | go_error_frame | enable_overload_cnt2 | go_overload_frame) delayed_dominant_cnt <=#Tp 0; else if (sample_point & (~sampled_bit) & ((error_cnt1 == 7) | (overload_cnt1 == 7))) delayed_dominant_cnt <=#Tp delayed_dominant_cnt + 1'b1; end //被动计数 always @ (posedge clk or posedge rst) begin if (rst) passive_cnt <= 0; else if (reset_mode | error_frame_ended | go_error_frame | go_overload_frame) passive_cnt <=#Tp 0; else if (sample_point & (passive_cnt < 5)) begin if (error_frame_q & (~enable_error_cnt2) & (sampled_bit == sampled_bit_q)) passive_cnt <=#Tp passive_cnt + 1'b1; else passive_cnt <=#Tp 0; end end // 传输超载帧always @ (posedge clk or posedge rst)beginif (rst)overload_frame <= 1'b0;else if (reset_mode | overload_frame_ended | go_error_frame)overload_frame <=#Tp 1'b0;else if (go_overload_frame)overload_frame <=#Tp 1'b1;endalways @ (posedge clk or posedge rst)beginif (rst)overload_cnt1 <= 1'b0;else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)overload_cnt1 <=#Tp 1'b0;else if (overload_frame & tx_point & (overload_cnt1 < 7))overload_cnt1 <=#Tp overload_cnt1 + 1'b1;endassign overload_flag_over = sample_point & (overload_cnt1 == 7) & (~enable_overload_cnt2);always @ (posedge clk or posedge rst)beginif (rst)enable_overload_cnt2 <= 1'b0;else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)enable_overload_cnt2 <=#Tp 1'b0;else if (overload_frame & (overload_flag_over & sampled_bit))enable_overload_cnt2 <=#Tp 1'b1;endalways @ (posedge clk or posedge rst)beginif (rst)overload_cnt2 <= 0;else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)overload_cnt2 <=#Tp 0;else if (enable_overload_cnt2 & tx_point)overload_cnt2 <=#Tp overload_cnt2 + 1'b1;endalways @ (posedge clk or posedge rst)beginif (rst)overload_frame_blocked <= 0;else if (reset_mode | go_error_frame | go_rx_id1)overload_frame_blocked <=#Tp 0;else if (go_overload_frame & overload_frame) // This is a second sequentialoverloadoverload_frame_blocked <=#Tp 1'b1;endassign send_ack = (~tx_state) & rx_ack & (~err) & (~listen_only_mode);always @ (posedge clk or posedge rst)beginif (rst)tx <= 1'b1;else if (reset_mode) // Resettx <=#Tp 1'b1;else if (tx_point)beginif (tx_state) // 传输报文tx <=#Tp ((~bit_de_stuff_tx) & tx_bit) | (bit_de_stuff_tx & (~tx_q));else if (send_ack) // 应答tx <=#Tp 1'b0;else if (overload_frame) //传输超载帧beginif (overload_cnt1 < 6)tx <=#Tp 1'b0;elsetx <=#Tp 1'b1;endelse if (error_frame) // 传输错误帧beginif (error_cnt1 < 6)beginif (node_error_passive)tx <=#Tp 1'b1;elsetx <=#Tp 1'b0;endelsetx <=#Tp 1'b1;endelsetx <=#Tp 1'b1;endendalways @ (posedge clk)beginif (tx_point)tx_q <=#Tp tx & (~go_early_tx_latched);end//延迟发送数据always @ (posedge clk)begintx_point_q <=#Tp tx_point;end



3.5 CRC 校验

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

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

CRC-RG=0 //初始化移位寄存器REPEATCRCNXT = NXTBIT EXOR CRC-RG(14);CRC-RG(14:1) = CRC-RG(13:0) //寄存器左移一位CRC-RG(0) = 0;IF CRCNXT THENCRC-RG(14:0) = CRC-RG(14:0) EXOR (4599H)END IFUNTIL(CRC 序列开始或者存在一个出错状态)


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

  assign crc_next = data ^ crc[14];  assign crc_tmp = {crc[13:0], 1'b0};  //CRC 校验  always @ (posedge clk)  begin    if(initialize)      crc <= #Tp 0;    else if (enable)      begin        if (crc_next)          crc <= #Tp crc_tmp ^ 15'h4599;        else          crc <= #Tp crc_tmp;      end  end



3.6 FIFO

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

assign write_length_info = (~wr) & wr_q;
//延迟写信号 always @ (posedge clk or posedge rst) begin if (rst) wr_q <= 0; else if (reset_mode) wr_q <=#Tp 0; else wr_q <=#Tp wr; end // 数据长度计数器 always @ (posedge clk or posedge rst) begin if (rst) len_cnt <= 0; else if (reset_mode | write_length_info) len_cnt <=#Tp 1'b0; else if (wr & (~fifo_full)) len_cnt <=#Tp len_cnt + 1'b1; end
// 写信息指针 always @ (posedge clk or posedge rst) begin if (rst) wr_info_pointer <= 0; else if (reset_mode) wr_info_pointer <=#Tp 0; else if (write_length_info & (~info_full))    wr_info_pointer <=#Tp wr_info_pointer + 1'b1; end //读信息指针 always @ (posedge clk or posedge rst) begin if (rst) rd_info_pointer <= 0; else if (reset_mode) rd_info_pointer <=#Tp 0; else if (release_buffer & (~fifo_empty)) rd_info_pointer <=#Tp rd_info_pointer + 1'b1; end
// 读指针 always @ (posedge clk or posedge rst) begin if (rst) rd_pointer <= 0; else if (release_buffer & (~fifo_empty)) rd_pointer <=#Tp rd_pointer + length_info; else if (reset_mode) rd_pointer <=#Tp 0; end // 写指针 always @ (posedge clk or posedge rst) begin if (rst) wr_pointer <= 0; else if (wr & (~fifo_full)) wr_pointer <=#Tp wr_pointer + 1'b1; else if (reset_mode) wr_pointer <=#Tp 0; end
//锁存 always @ (posedge clk or posedge rst) begin if (rst) latch_overrun <= 0; else if (reset_mode | write_length_info) latch_overrun <=#Tp 0; else if (wr & fifo_full) latch_overrun <=#Tp 1'b1; end //统计在 FIFO 中的数据 always @ (posedge clk or posedge rst) begin if (rst) fifo_cnt <= 0; else if (wr & (~release_buffer) & (~fifo_full)) fifo_cnt <=#Tp fifo_cnt + 1'b1; else if ((~wr) & release_buffer & (~fifo_empty)) fifo_cnt <=#Tp fifo_cnt - length_info; else if (wr & release_buffer & (~fifo_full) & (~fifo_empty)) fifo_cnt <=#Tp fifo_cnt - length_info + 1'b1; else if (reset_mode) fifo_cnt <=#Tp 0; end assign fifo_full = fifo_cnt == 64; assign fifo_empty = fifo_cnt == 0; //统计在 length_fifo 和 overrun_info fifo 中的数据 always @ (posedge clk or posedge rst) begin if (rst) info_cnt <= 0; else if (write_length_info ^ release_buffer) begin if (release_buffer & (~info_empty)) info_cnt <=#Tp info_cnt - 1'b1; else if (write_length_info & (~info_full)) info_cnt <=#Tp info_cnt + 1'b1; end end assign info_full = info_cnt == 64; assign info_empty = info_cnt == 0;
//选择用来读数据的 FIFO 的地址 always @ (extended_mode or rd_pointer or addr) begin if (extended_mode) // extended mode begin read_address <= rd_pointer + (addr - 8'd16); end else // normal mode begin read_address <= rd_pointer + (addr - 8'd20); end end always @ (posedge clk) begin if (wr & (~fifo_full)) fifo[wr_pointer] <=#Tp data_in; end //从 FIFO 中读数据 assign data_out = fifo[read_address]; //写到 length_fifo always @ (posedge clk) begin if (write_length_info & (~info_full)) length_fifo[wr_info_pointer] <=#Tp len_cnt; end
// 读 length_fifo 中的数据 assign length_info = length_fifo[rd_info_pointer];
// overrun_info always @ (posedge clk) begin if (write_length_info & (~info_full)) overrun_info[wr_info_pointer] <=#Tp latch_overrun | (wr & fifo_full); end
// 读取 overrun assign overrun = overrun_info[rd_info_pointer]


四、程序的仿真与测试


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

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

//强制关闭总线任务task forced_bus_off; // Forcing bus-off by writinf to tx_err_cnt registerbegin//切换到复位模式write_register(8'd0, {7'h0, `CAN_MODE_RESET});// 设置时钟分频寄存器write_register(8'd31, {1'b1, 7'h0}); // Setting the extended mode (not normal)// 写数据到寄存器中write_register(8'd15, 255);// 切换复位模式write_register(8'd0, {7'h0, ~(`CAN_MODE_RESET)});#2500000;// 切换复位模式write_register(8'd0, {7'h0, `CAN_MODE_RESET});// 写数据到寄存器中write_register(8'd15, 245);//关闭复位模式write_register(8'd0, {7'h0, ~(`CAN_MODE_RESET)});#1000000;endendtask // forced_bus_off


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

//发送一个基本格式的帧task manual_frame_basic;begin// 切换到复位模式  write_register(8'd0, {7'h0, (`CAN_MODE_RESET)});//设置寄存器  write_register(8'd4, 8'h28); // acceptance code  write_register(8'd5, 8'hff); // acceptance mask  repeat (100) @ (posedge clk);// 切换复位模式  write_register(8'd0, {7'h0, ~(`CAN_MODE_RESET)});// 模块复位后设置总线空闲  repeat (11) send_bit(1);  write_register(8'd10, 8'h55); // Writing ID[10:3] = 0x55  write_register(8'd11, 8'h57); // Writing ID[2:0] = 0x2, rtr = 1, length = 7  write_register(8'd12, 8'h00); // data byte 1  write_register(8'd13, 8'h00); // data byte 2  write_register(8'd14, 8'h00); // data byte 3  write_register(8'd15, 8'h00); // data byte 4  write_register(8'd16, 8'h00); // data byte 5  write_register(8'd17, 8'h00); // data byte 6  write_register(8'd18, 8'h00); // data byte 7  write_register(8'd19, 8'h00); // data byte 8  tx_bypassed = 1; // When this signal is on, tx is not looped back to the rx.    fork    begin    self_reception_request_command;    end        begin      #2200;      repeat (1)      //开始发送数据      begin        send_bit(0); // 帧起始        send_bit(0); // ID        send_bit(1); // ID        send_bit(0); // ID        send_bit(1); // ID        send_bit(0); // ID        send_bit(1); // ID        send_bit(0); // ID        send_bit(1); // ID        send_bit(0); // ID        send_bit(1); // ID        send_bit(0); // ID        send_bit(1); // RTR        send_bit(0); // IDE        send_bit(0); // r0        send_bit(0); // DLC        send_bit(1); // DLC        send_bit(1); // DLC        send_bit(1); // DLC        send_bit(1); // CRC        send_bit(1); // CRC        send_bit(0); // CRC stuff        send_bit(0); // CRC 6        send_bit(0); // CRC        send_bit(0); // CRC        send_bit(0); // CRC        send_bit(1); // CRC stuff        send_bit(0); // CRC 0        send_bit(0); // CRC        send_bit(1); // CRC        send_bit(0); // CRC        send_bit(1); // CRC 5        send_bit(1); // CRC        send_bit(0); // CRC        send_bit(1); // CRC        send_bit(1); // CRC b        send_bit(1); // CRC DELIM        send_bit(0); // ACK        send_bit(1); // ACK DELIM        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // EOF        send_bit(1); // INTER        send_bit(1); // INTER        send_bit(1); // INTER        end // repeat      end          join    //从接收缓冲中读取数据    read_receive_buffer;    release_rx_buffer_command;    read_receive_buffer;    release_rx_buffer_command;    read_receive_buffer;    #4000000;      endendtask // manual_frame_basic


五、总结



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


END


往期精选 

 
 

【免费】FPGA工程师招聘平台

今日说“法”:FPGA芯片如何选型?

SANXIN-B01开发板verilog教程V3电子版

学员笔记连载 | FPGA Zynq 千兆以太网回环

就业班学员学习笔记分享:FPGA之HDMI、以太网篇

求职面试 | FPGA或IC面试题最新汇总篇

FPGA项目开发:204B实战应用-LMK04821代码详解(二)

项目合作 | 承接FPGA项目公告

FPGA技术江湖广发江湖帖

无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。


FPGA技术江湖微信交流群

加群主微信,备注姓名+公司/学校+岗位/专业进群


FPGA技术江湖QQ交流群

备注姓名+公司/学校+岗位/专业进群

FPGA技术江湖 任何技术的学习就好比一个江湖,对于每一位侠客都需要不断的历练,从初入江湖的小白到归隐山林的隐世高人,需要不断的自我感悟自己修炼,让我们一起仗剑闯FPGA乃至更大的江湖。
评论
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 106浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 86浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 83浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 41浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 70浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 51浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 98浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 66浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 70浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 95浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 37浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 62浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦