今天给大侠带来基于FPGA的CAN总线控制器的设计,包括CAN 总线协议解析以及 CAN 通信控制器程序基本框架、CAN 通信控制器的具体实现、程序的仿真与测试以及总结。篇幅较长,话不多说,上货。
导读
CAN 总线(Controller Area Network)是控制器局域网的简称,是 20 世纪 80 年代初德国 BOSCH 公司为解决现代汽车中众多的控制与测试仪器之间的数据交换而开发的一种串行数据通信协议。目前,CAN 总线已经被列入 ISO 国际标准,称为 ISO11898。CAN 总线已经成为工业数据通信的主流技术之一。
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 所示。
这 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 所示。
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 所示。
三、CAN 通信控制器的具体实现
各模块的组织结构如图 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(
1]), :
1]), :
.we(we_mode),
.clk(clk),
.rst(rst)
);
can_register_asyn #(3, 0) MODE_REG_EXT(
1]), :
1]), :
& 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),
& 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),
& ~transmitting)
);
can_register_asyn_syn #(2, 2'h0) COMMAND_REG(
2]), :
2]), :
.we(we_command),
.clk(clk),
.rst(rst),
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),
& (~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;
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(
0]), :
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
clkout_div <= 0; :
clkout_div <= 1; :
clkout_div <= 2; :
clkout_div <= 3; :
clkout_div <= 4; :
clkout_div <= 5; :
clkout_div <= 6; :
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
// read
begin
if (extended_mode) // EXTENDED mode (Different register map depends on mode)
begin
case(addr)
data_out_tmp <= {4'b0000, mode_ext[3:1], mode[0]}; :
data_out_tmp <= 8'h0; :
data_out_tmp <= status; :
data_out_tmp <= irq_reg; :
data_out_tmp <= irq_en_ext; :
data_out_tmp <= bus_timing_0; :
data_out_tmp <= bus_timing_1; :
data_out_tmp <= {3'h0, arbitration_lost_capture[4:0]}; :
data_out_tmp <= error_capture_code; :
data_out_tmp <= error_warning_limit; :
data_out_tmp <= rx_err_cnt; :
data_out_tmp <= tx_err_cnt; :
data_out_tmp <= acceptance_code_0; :
data_out_tmp <= acceptance_code_1; :
data_out_tmp <= acceptance_code_2; :
data_out_tmp <= acceptance_code_3; :
data_out_tmp <= acceptance_mask_0; :
data_out_tmp <= acceptance_mask_1; :
data_out_tmp <= acceptance_mask_2; :
data_out_tmp <= acceptance_mask_3; :
data_out_tmp <= 8'h0; :
data_out_tmp <= 8'h0; :
data_out_tmp <= 8'h0; :
data_out_tmp <= 8'h0; :
data_out_tmp <= 8'h0; :
data_out_tmp <= {1'b0, rx_message_counter}; :
data_out_tmp <= clock_divider; :
default: data_out_tmp <= 8'h0;
endcase
end
else // BASIC mode
begin
case(addr)
data_out_tmp <= {3'b001, mode_basic[4:1], mode[0]}; :
data_out_tmp <= 8'hff; :
data_out_tmp <= status; :
data_out_tmp <= {4'hf, irq_reg[3:0]}; :
data_out_tmp <= reset_mode? acceptance_code_0 : 8'hff; :
data_out_tmp <= reset_mode? acceptance_mask_0 : 8'hff; :
data_out_tmp <= reset_mode? bus_timing_0 : 8'hff; :
data_out_tmp <= reset_mode? bus_timing_1 : 8'hff; :
data_out_tmp <= reset_mode? 8'hff : tx_data_0; :
data_out_tmp <= reset_mode? 8'hff : tx_data_1; :
data_out_tmp <= reset_mode? 8'hff : tx_data_2; :
data_out_tmp <= reset_mode? 8'hff : tx_data_3; :
data_out_tmp <= reset_mode? 8'hff : tx_data_4; :
data_out_tmp <= reset_mode? 8'hff : tx_data_5; :
data_out_tmp <= reset_mode? 8'hff : tx_data_6; :
data_out_tmp <= reset_mode? 8'hff : tx_data_7; :
data_out_tmp <= reset_mode? 8'hff : tx_data_8; :
data_out_tmp <= reset_mode? 8'hff : tx_data_9; :
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 <=
else
clk_cnt <=
end
//产生定义波特率的一般使能信号
always @ (posedge clk or posedge rst)
begin
if (rst)
clk_en <= 1'b0;
else if (clk_cnt == (preset_cnt-1))
clk_en <=
else
clk_en <=
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 <=
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 <=
else if (go_seg1)
sync <=
end
assign tx_point = go_sync;
//片断 seg1
always @ (posedge clk or posedge rst)
begin
if (rst)
seg1 <= 1;
else if (go_seg1)
seg1 <=
else if (go_seg2)
seg1 <=
end
//片断 seg2
always @ (posedge clk or posedge rst)
begin
if (rst)
seg2 <= 0;
else if (go_seg2)
seg2 <=
else if (go_sync | go_seg1)
seg2 <=
end
//Quant 计数器
always @ (posedge clk or posedge rst)
begin
if (rst)
quant_cnt <= 0;
else if (go_sync | go_seg1 | go_seg2)
quant_cnt <=
else if (clk_en)
quant_cnt <=
end
//当探测到后沿时,片断 seg1 被延时
begin
if (rst)
delay <= 0;
else if (clk_en & resync & seg1)
delay <=
else if (go_sync | go_seg1)
delay <=
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 <=
sampled_bit_q <=
if (triple_sampling)
sampled_bit <=
else
sampled_bit <=
end
end
else
sample_point <=
end
//阻塞同步
always @ (posedge clk or posedge rst)
begin
if (rst)
sync_blocked <=
else if (clk_en)
begin
if (hard_sync | resync)
sync_blocked <=
else if (seg2 & quant_cnt == time_segment2)
sync_blocked <=
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 <=
else if (reset_mode)
resync_blocked <=
else if (hard_sync)
resync_blocked <=
end
3.4 位数据流处理器——Bit Stream Processor
位数据流处理器负责完成程序中所有有关数据的操作。位数据流处理器实际上就是一个序列发生器,它控制发送缓冲器、接收 FIFO 和 CAN 总线之间的数据流,同时它也执行错误检测、仲裁、位填充和 CAN 总线错误处理功能。位数据流处理器程序结构如图 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 <=
else if (go_rx_idle)
rx_idle <=
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 <=
else if (go_rx_id1)
rx_id1 <=
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 <=
else if (go_rx_rtr1)
rx_rtr1 <=
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 <=
else if (go_rx_ide)
rx_ide <=
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 <=
else if (go_rx_id2)
rx_id2 <=
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 <=
else if (go_rx_rtr2)
rx_rtr2 <=
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 <=
else if (go_rx_r1)
rx_r1 <=
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 <=
else if (go_rx_r0)
rx_r0 <=
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 <=
else if (go_rx_dlc)
rx_dlc <=
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 <=
else if (go_rx_data)
rx_data <=
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 <=
else if (go_rx_crc)
rx_crc <=
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 <=
else if (go_rx_crc_lim)
rx_crc_lim <=
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 <=
else if (go_rx_ack)
rx_ack <=
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 <=
else if (go_rx_ack_lim)
rx_ack_lim <=
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 <=
else if (go_rx_eof)
rx_eof <=
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 <=
else if (go_rx_inter)
rx_inter <=
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 <=
end
// rtr1 位
always @ (posedge clk or posedge rst)
begin
if (rst)
rtr1 <= 0;
else if (sample_point & rx_rtr1 & (~bit_de_stuff))
rtr1 <=
end
// rtr2 位
always @ (posedge clk or posedge rst)
begin
if (rst)
rtr2 <= 0;
else if (sample_point & rx_rtr2 & (~bit_de_stuff))
rtr2 <=
end
// ide 位
always @ (posedge clk or posedge rst)
begin
if (rst)
ide <= 0;
else if (sample_point & rx_ide & (~bit_de_stuff))
ide <=
end
// 获得数据长度
always @ (posedge clk or posedge rst)
begin
if (rst)
data_len <= 0;
else if (sample_point & rx_dlc & (~bit_de_stuff))
data_len <=
end
// 获得数据
always @ (posedge clk or posedge rst)
begin
if (rst)
tmp_data <= 0;
else if (sample_point & rx_data & (~bit_de_stuff))
tmp_data <=
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 <=
else
write_data_to_tmp_fifo <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
byte_cnt <= 0;
else if (write_data_to_tmp_fifo)
byte_cnt <=
else if (reset_mode | (sample_point & go_rx_crc_lim))
byte_cnt <=
end
always @ (posedge clk)
begin
if (write_data_to_tmp_fifo)
tmp_fifo[byte_cnt] <=
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 <=
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 <=
else if (sample_point & (~bit_de_stuff))
bit_cnt <=
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 <=
else if (rx_eof)
eof_cnt <=
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 <=
else if (bit_de_stuff_reset)
bit_stuff_cnt_en <=
end
//位填充计数器
always @ (posedge clk or posedge rst)
begin
if (rst)
bit_stuff_cnt <= 1;
else if (bit_de_stuff_reset)
bit_stuff_cnt <=
else if (sample_point & bit_stuff_cnt_en)
begin
if (bit_stuff_cnt == 5)
bit_stuff_cnt <=
else if (sampled_bit == sampled_bit_q)
bit_stuff_cnt <=
else
bit_stuff_cnt <=
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 <=
else if (bit_de_stuff_reset)
bit_stuff_cnt_tx_en <=
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 <=
else if (tx_point_q & bit_stuff_cnt_en)
begin
if (bit_stuff_cnt_tx == 5)
bit_stuff_cnt_tx <=
else if (tx == tx_q)
bit_stuff_cnt_tx <=
else
bit_stuff_cnt_tx <=
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 <=
node_bus_off_q <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
crc_enable <= 1'b0;
else if (go_crc_enable)
crc_enable <=
else if (reset_mode | rst_crc_enable)
crc_enable <=
end
//CRC 校验错误
always @ (posedge clk or posedge rst)
begin
if (rst)
crc_err <= 1'b0;
else if (go_rx_ack)
crc_err <=
else if (reset_mode | error_frame_ended)
crc_err <=
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 <=
else if (ack_err)
ack_err_latched <=
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 <=
else if (bit_err)
bit_err_latched <=
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 <=
else if (transmitter & node_error_passive & ack_err)
rule3_exc1_1 <=
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 <=
else if (rule3_exc1_1)
rule3_exc1_2 <=
else if ((error_cnt1 < 7) & sample_point & (~sampled_bit))
rule3_exc1_2 <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
rule3_exc2 <= 1'b0;
else if (reset_mode | error_flag_over)
rule3_exc2 <=
else if (transmitter & stuff_err & arbitration_field & sample_point & tx & (~sampled_bit))
rule3_exc2 <=
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 <=
else if (stuff_err)
stuff_err_latched <=
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 <=
else if (form_err)
form_err_latched <=
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 <=
else if (go_rx_inter & id_ok & (~error_frame_ended) & ((~tx_state) | self_rx_request))
wr_fifo <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
header_cnt <= 0;
else if (reset_wr_fifo)
header_cnt <=
else if (wr_fifo & storing_header)
header_cnt <=
end
//数据计数器
always @ (posedge clk or posedge rst)
begin
if (rst)
data_cnt <= 0;
else if (reset_wr_fifo)
data_cnt <=
else if (wr_fifo)
data_cnt <=
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 <=
else if (go_error_frame)
error_frame <=
end
always @ (posedge clk)
begin
if (sample_point)
error_frame_q <=
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 <=
else if (error_frame & tx_point & (error_cnt1 < 7))
error_cnt1 <=
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 <=
else if (error_flag_over)
error_flag_over_blocked <=
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 <=
else if (error_frame & (error_flag_over & sampled_bit))
enable_error_cnt2 <=
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 <=
else if (enable_error_cnt2 & tx_point)
error_cnt2 <=
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 <=
else if (sample_point & (~sampled_bit) & ((error_cnt1 == 7) | (overload_cnt1 == 7)))
delayed_dominant_cnt <=
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 <=
else if (sample_point & (passive_cnt < 5))
begin
if (error_frame_q & (~enable_error_cnt2) & (sampled_bit == sampled_bit_q))
passive_cnt <=
else
passive_cnt <=
end
end
// 传输超载帧
always @ (posedge clk or posedge rst)
begin
if (rst)
overload_frame <= 1'b0;
else if (reset_mode | overload_frame_ended | go_error_frame)
overload_frame <=
else if (go_overload_frame)
overload_frame <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
overload_cnt1 <= 1'b0;
else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)
overload_cnt1 <=
else if (overload_frame & tx_point & (overload_cnt1 < 7))
overload_cnt1 <=
end
assign overload_flag_over = sample_point & (overload_cnt1 == 7) & (~enable_overload_cnt2);
always @ (posedge clk or posedge rst)
begin
if (rst)
enable_overload_cnt2 <= 1'b0;
else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)
enable_overload_cnt2 <=
else if (overload_frame & (overload_flag_over & sampled_bit))
enable_overload_cnt2 <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
overload_cnt2 <= 0;
else if (reset_mode | overload_frame_ended | go_error_frame | go_overload_frame)
overload_cnt2 <=
else if (enable_overload_cnt2 & tx_point)
overload_cnt2 <=
end
always @ (posedge clk or posedge rst)
begin
if (rst)
overload_frame_blocked <= 0;
else if (reset_mode | go_error_frame | go_rx_id1)
overload_frame_blocked <=
else if (go_overload_frame & overload_frame) // This is a second sequential
overload
overload_frame_blocked <=
end
assign send_ack = (~tx_state) & rx_ack & (~err) & (~listen_only_mode);
always @ (posedge clk or posedge rst)
begin
if (rst)
tx <= 1'b1;
else if (reset_mode) // Reset
tx <=
else if (tx_point)
begin
if (tx_state) // 传输报文
tx <=
else if (send_ack) // 应答
tx <=
else if (overload_frame) //传输超载帧
begin
if (overload_cnt1 < 6)
tx <=
else
tx <=
end
else if (error_frame) // 传输错误帧
begin
if (error_cnt1 < 6)
begin
if (node_error_passive)
tx <=
else
tx <=
end
else
tx <=
end
else
tx <=
end
end
always @ (posedge clk)
begin
if (tx_point)
tx_q <=
end
//延迟发送数据
always @ (posedge clk)
begin
tx_point_q <=
end
3.5 CRC 校验
CAN 节点中设有错误检测、标定和自检等措施。检测错误包括多种方式,其中最常用、最有效的一种是 CRC 校验。CRC 序列由循环冗余校验码求得的帧检查序组成。为实现 CRC 计算,被除的多项式系数由包括帧起始、仲裁字段、控制字段、数据字段在内的无填充位数据流给出,其 15 个最低位的系数为 0。
此多项式被发生器产生的下列多项式除(系数为模 2 运算):1 3478101415XXXXXXX +++++++该多项式除法的余数即为发向总线的 CRC 序列。为完成此运算,可以使用一个 15 位的移位寄存器 CRC-RG(14:0)。被除多项式位数据流由帧起始到数据字段结束的无填充序列给定,如果以 NXTBIT 标记该位数据流的下一位,则 CRC 序列可以用如下的方式求得:
CRC-RG=0 //初始化移位寄存器
REPEAT
CRCNXT = NXTBIT EXOR CRC-RG(14);
CRC-RG(14:1) = CRC-RG(13:0) //寄存器左移一位
CRC-RG(0) = 0;
IF CRCNXT THEN
CRC-RG(14:0) = CRC-RG(14:0) EXOR (4599H)
END IF
UNTIL(CRC 序列开始或者存在一个出错状态)
完成数据 CRC 校验的主要代码如下:
assign crc_next = data ^ crc[14];
assign crc_tmp = {crc[13:0], 1'b0};
//CRC 校验
always @ (posedge clk)
begin
if(initialize)
crc <=
else if (enable)
begin
if (crc_next)
crc <=
else
crc <=
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 register
begin
//切换到复位模式
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)});
// 切换复位模式
write_register(8'd0, {7'h0, `CAN_MODE_RESET});
// 写数据到寄存器中
write_register(8'd15, 245);
//关闭复位模式
write_register(8'd0, {7'h0, ~(`CAN_MODE_RESET)});
end
endtask // 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;
end
endtask // manual_frame_basic
五、总结
本篇通过一个实例讲解如何用 FPGA 实现 CAN 总线通信控制器。首先讲解了 CAN 总线协议的有关内容,然后介绍了一种常用的 CAN 通信控制器 SJA1000 的主要特点。接下来讲解程序的主要框架和具体代码。最后通过一个测试程序验证了程序。这个实例为读者实现自己的 CAN总线通信控制器提供了一个可以应用的案例。
END
往期精选
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注姓名+公司/学校+岗位/专业进群
FPGA技术江湖QQ交流群
备注姓名+公司/学校+岗位/专业进群