FPGA实现CAN通信
1、CAN协议物理层和协议层
2、传输的波特率
3、FPGA实现思路
4、FPGA实现代码
1、CAN物理层和协议层
CAN与串口类似,都是异步通信,利用两根差分线来进行信号的传输。
在多节点进行数据传输时主要分为遵循ISO11898标准的高速短距离闭环形式和遵循ISO11519标准的低速远距离开环网络。这两种形式主要是在硬件设计时根据实际应用情况加入120欧姆或者2.2千欧姆电阻。
在CAN通信时信号逻辑和平时常用的电平表示不太一样,根据标准电平表示形式如下图:
CAN报文类型有5种,分别是数据帧、遥控帧、错误帧、过载帧、帧间隔。而我们常用的是数据帧,数据帧分为标准数据帧和扩展数据帧两种。数据帧结构如下图:
数据帧是以一个下降沿的电平来开始界定开始的。以7个连续隐性电平结束的。数据帧中间包含帧起始、仲裁段、控制段、数据段、CRC段、ACK段和帧结束段。
帧起始SOF段(Start Of Frame),帧起始信号只有一个数据位,是一个显性电平,它用于监测数据传输的开始,通过电平跳变沿来进行数据起始位的确定。
仲裁段:内容是数据帧的ID信息,标准帧ID长度是11位,扩展帧为18位。CAN 协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由帧的ID决定的,对于重要信息给与一个优先级较高的ID,这样数据就能及时的发送出去。而ID优先级的仲裁原则是由物理层决定的,总线状态总是显性电平掩盖隐形电平,因此显性ID优先级较高。
仲裁段还包括(1)RTR 位(Remote Transmission Request Bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。
(2) IDE 位(Identifier Extension Bit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。
(3) SRR 位(Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的
RTR 位。由于扩展帧中的SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个ID相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。
控制段:在控制段中的r1 和r0 为保留位,默认设置为显性位。它最主要的是DLC 段(Data
Length Code),译为数据长度码,它由4 个数据位组成,用于表示本报文中的数据段含有多
少个字节,DLC 段表示的数字为0~8。
数据段:数据段为数据帧的核心内容,它是节点要发送的原始信息,由0~8 个字节组成,MSB先行。
CRC 段:为了保证报文的正确传输,CAN 的报文包含了一段15 位的CRC 校验码,一旦接收节点算出的CRC 码跟接收到的CRC 码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC 部分的计算一般由CAN 控制器硬件完成,出错时的处理则由软件控制最大重发数。
ACK 段:ACK 段包括一个ACK 槽位,和ACK 界定符位。类似I2C 总线,在ACK 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在ACK 槽和帧结束之间由ACK 界定符间隔开。
帧结束EOF 段(End Of Frame),帧结束段由发送节点发送的7 个隐性位表示结束。
2、传输的波特率
CAN由于是异步通信,通信波特率与串口波特率定义类似,波特率的定义有每个子的长度来确定的。CAN的通信距离与波特率存在负相关关系,波特率越高,传输距离越短。CAN通信波特率与传输距离关系如下图:
3、FPGA实现思路
在进行FPGA实现时主要是实现一个完备的状态转移状态机。在设计状态机时需要对最复杂的包格式进行设计。通过对控制段进行判断来跳转到不同类型帧格式的状态,根据数据长度来完成对数据的接收和发送。由于FPGA实现完备的CAN收发驱动并不是所有项目所必须的,因此根据不同项目来进行特定包数据的收发。
通过上面描述对CAN数据格式有了一个基本上认识,下面是通过示波器抓取CAN标准数据帧波形,示波器正连接CANH端,示波器负极连接CANL端。波特率是10Kbps,有效数据长度8字节。
在实现波特率可调的数据收发控制时需要注意的是每个波特数据的采样点。CAN数据采样时序如下图所示,一般采样点都是在数据稳当的中间点位置,因此在设计FPGA中CAN模块的时钟频率应当是数据波特率的20倍。
在以10Kbps采样率位例,CAN收发模块时钟设计如下:
//
//数据速率位10Kbps,一个数据位时间为10us.数据采集在5us时刻。
CAN波特率设置
reg [7:0] can_div ; //500倍分频,一个数据位分为20份
reg can_clk ;
always @(posedge clk_100m or negedge rst_n )begin
if(rst_n==1'b0) begin
can_div <= 'b0 ;
end else if (can_div==249) begin
can_div <= 'b0 ;
end else begin
can_div <= can_div + 1'b1 ;
end
end
always @(posedge clk_100m or negedge rst_n )begin
if(rst_n==1'b0) begin
can_clk <= 'b0 ;
end else if (can_div==249) begin
can_clk <= can_clk + 1'b1 ;
end
end
//时钟经过BUFG缓冲
wire can_clk_o ;
BUFG can_clk_obuf (.I(can_clk), .O(can_clk_o));
数据的收发需要根据实际的项目情况进行组包控制,这里就不进行细致描述了。
CAN实现数据的收发两个过程中对应FPGA来说由于接收相对复杂,就以接收模块进行描述。
数据的接收过程就按照一般的状态机进行设计就行,需要注意的是不同类型帧的跳转是在控制段进行了,因此在控制段会发生状态的跳转。CAN接收状态的状态机实现如下图:
这个状态机的实现并不复杂,主要是对帧类型进行判断。然后根据数据长度把数据解析出来。下面是实现CAN数据接收代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date:
// Design Name:
// Module Name: can_rx
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module can_rx(
input wire can_clk ,
input wire rst_n ,
input wire can_rx ,
output reg can_ack_out_low ,
output reg can_id_out_en ,
output reg [10:0] can_id_out ,
output reg can_data_out_en ,
output reg [63:0] can_data_out
);
reg [8:0] state ;
reg can_rx_t ;
reg error_state ;
reg [9:0] error_data ;
reg [4:0] one_bit_cont ;
reg [10:0] can_id ;
reg [6:0] bit_cont ;
reg id_en_flag ;
reg contral_flag ;
reg data_en_flag ;
reg cic_en_flag ;
reg can_rx_en_flag ;
reg can_rx_unen_flag ;
reg [4:0] can_continuity_data ;
reg can_continuity_data_flag ;
reg [4:0] can_id_data_cont ;
reg [3:0] can_contral_data_cont ;
reg [6:0] can_data_data_cont ;
reg [4:0] can_cic_data_cont ;
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_rx_t <= 'b0 ;
end else begin
can_rx_t <= can_rx ;
end
end
parameter state_idle = 9'b000000000 ; //状态机初始化
parameter state_start = 9'b000000001 ; //监测到开始标志
parameter state_sof = 9'b000000010 ; //开始帧头第一位SOF
parameter state_id = 9'b000000100 ; //包ID
parameter state_control = 9'b000001000 ; //标准帧控制段
parameter state_data = 9'b000010000 ; //数据段
parameter state_crc = 9'b000100000 ; //CRC段
parameter state_ack = 9'b001000000 ; //ACK段
parameter state_eof = 9'b010000000 ; //帧结束段
parameter state_end = 9'b100000000 ; //状态机结束状态
parameter bit_flag_no = 5'b10011 ;
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
state <= 'b0 ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
id_en_flag <= 'b0 ;
contral_flag <= 'b0 ;
data_en_flag <= 'b0 ;
cic_en_flag <= 'b0 ;
can_rx_en_flag <= 'b0 ;
can_ack_out_low <= 'b1 ;
end else case(state)
state_idle: begin if ((can_rx_t==1'b1)&&(can_rx==1'b0)) begin
state <= state_sof ;
one_bit_cont <= 'b0 ;
can_rx_en_flag <= 'b1 ;
end else begin
state <= state_idle ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
id_en_flag <= 'b0 ;
contral_flag <= 'b0 ;
data_en_flag <= 'b0 ;
cic_en_flag <= 'b0 ;
can_rx_en_flag <= 'b0 ;
can_ack_out_low <= 'b1 ;
end
end
state_sof: begin if ((one_bit_cont==bit_flag_no)&&(can_rx==1'b0)) begin
state <= state_id ;
id_en_flag <= 'b1 ;
one_bit_cont <= 'b0 ;
end else if ((one_bit_cont<bit_flag_no)&&(can_rx==1'b0)) begin
state <= state_sof ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end
end
state_id: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==can_id_data_cont)) begin
state <= state_control ;
id_en_flag <= 'b0 ;
contral_flag <= 'b1 ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<can_id_data_cont)) begin
state <= state_id ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_id ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_control: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==can_contral_data_cont)) begin
state <= state_data ;
contral_flag <= 'b0 ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
data_en_flag <= 'b1 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<can_contral_data_cont)) begin
state <= state_control ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_control ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_data: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==can_data_data_cont)) begin
state <= state_crc ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
data_en_flag <= 'b0 ;
cic_en_flag <= 'b1 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<can_data_data_cont)) begin
state <= state_data ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_data ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_crc: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==can_cic_data_cont)) begin
state <= state_ack ;
can_ack_out_low <= 'b0 ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
cic_en_flag <= 'b0 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<can_cic_data_cont)) begin
state <= state_crc ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_crc ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_ack: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==1)) begin
state <= state_eof ;
can_ack_out_low <= 'b1 ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<1)) begin
state <= state_ack ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_ack ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_eof: begin if ((one_bit_cont==bit_flag_no)&&(bit_cont==5)) begin
state <= state_end ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
end else if ((one_bit_cont==bit_flag_no)&&(bit_cont<5)) begin
state <= state_eof ;
one_bit_cont <= 'b0 ;
bit_cont <= bit_cont + 1'b1 ;
end else if (one_bit_cont<bit_flag_no) begin
state <= state_eof ;
one_bit_cont <= one_bit_cont + 1'b1 ;
end else begin
state <= state_idle ;
end
end
state_end: begin
state <= state_idle ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
id_en_flag <= 'b0 ;
contral_flag <= 'b0 ;
data_en_flag <= 'b0 ;
cic_en_flag <= 'b0 ;
can_rx_en_flag <= 'b0 ;
can_ack_out_low <= 'b1 ;
end
default: begin
state <= state_idle ;
one_bit_cont <= 'b0 ;
bit_cont <= 'b0 ;
id_en_flag <= 'b0 ;
contral_flag <= 'b0 ;
data_en_flag <= 'b0 ;
cic_en_flag <= 'b0 ;
can_rx_en_flag <= 'b0 ;
can_ack_out_low <= 'b1 ;
end
endcase
end
//always @(posedge can_clk or negedge rst_n )begin
//if(rst_n==1'b0) begin
//error_data <= 'b0 ;
//end else if (can_rx_en_flag==1) begin
//error_data <= {error_data[9:0],can_rx } ;
//end else if (one_bit_cont==11) begin
//can_rx_unen_flag <= 1'b0 ;
//end else if (can_rx_en_flag==0)
//can_rx_unen_flag <= 'b0 ;
//end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_continuity_data <= 5'b11111 ;
can_continuity_data_flag <= 'b0 ;
end else if ((one_bit_cont==9)&&(can_rx_en_flag==1)) begin
can_continuity_data <= {can_continuity_data[3:0],can_rx} ;
can_continuity_data_flag <= 'b0 ;
end else if (((can_continuity_data==0)||(can_continuity_data==5'b11111))&&(one_bit_cont==10)&&(cic_en_flag==0)) begin
can_continuity_data_flag <= 'b1 ;
end else if (((can_continuity_data==0)||(can_continuity_data==5'b11111))&&(one_bit_cont==10)&&(cic_en_flag==1)&&(bit_cont<14)) begin
can_continuity_data_flag <= 'b1 ;
end else if (can_rx_en_flag==0) begin
can_continuity_data <= 5'b11111 ;
can_continuity_data_flag <= 'b0 ;
end else begin
can_continuity_data_flag <= 'b0 ;
end
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_rx_unen_flag <= 'b0 ;
end else if ((can_rx_en_flag==1)&&(can_continuity_data_flag==1)&&(cic_en_flag==0)) begin
can_rx_unen_flag <= 1'b1 ;
end else if ((can_rx_en_flag==1)&&(can_continuity_data_flag==1)&&(cic_en_flag==1)&&(bit_cont<14)) begin
can_rx_unen_flag <= 1'b1 ;
end else if (one_bit_cont==11) begin
can_rx_unen_flag <= 1'b0 ;
end else if (can_rx_en_flag==0)
can_rx_unen_flag <= 'b0 ;
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_id_data_cont <= 'd10 ;
end else if ((id_en_flag==1)&&(can_continuity_data_flag==1)) begin
can_id_data_cont <= can_id_data_cont + 1'b1 ;
end else if (id_en_flag==0)
can_id_data_cont <= 'd10 ;
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_contral_data_cont <= 'd6 ;
end else if ((contral_flag==1)&&(can_continuity_data_flag==1)) begin
can_contral_data_cont <= can_contral_data_cont + 1'b1 ;
end else if (contral_flag==0)
can_contral_data_cont <= 'd6 ;
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_data_data_cont <= 'd63 ;
end else if ((data_en_flag==1)&&(can_continuity_data_flag==1)) begin
can_data_data_cont <= can_data_data_cont + 1'b1 ;
end else if (data_en_flag==0)
can_data_data_cont <= 'd63 ;
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_cic_data_cont <= 'd15 ;
end else if ((cic_en_flag==1)&&(can_continuity_data_flag==1)) begin
can_cic_data_cont <= can_cic_data_cont + 1'b1 ;
end else if (cic_en_flag==0)
can_cic_data_cont <= 'd15 ;
end
///
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_id_out <= 'b0 ;
end else if ((one_bit_cont==9)&&(id_en_flag==1)&&(can_rx_unen_flag==0)) begin
can_id_out <= {can_id_out[9:0],can_rx } ;
end
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_id_out_en <= 'b0 ;
end else if ((one_bit_cont==9)&&(bit_cont==can_id_data_cont)&&(id_en_flag==1)) begin
can_id_out_en <= 1'b1 ;
end else
can_id_out_en <= 'b0 ;
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_data_out <= 'b0 ;
end else if ((one_bit_cont==9)&&(data_en_flag==1)&&(can_rx_unen_flag==0)) begin
can_data_out <= {can_data_out[62:0],can_rx } ;
end
end
always @(posedge can_clk or negedge rst_n )begin
if(rst_n==1'b0) begin
can_data_out_en <= 'b0 ;
end else if ((bit_cont==can_data_data_cont)&&(one_bit_cont==9)&&(data_en_flag==1)) begin
can_data_out_en <= 1'b1 ;
end else
can_data_out_en <= 'b0 ;
end
/
endmodule
在完成上述代码编写后通过CAN收发器发送数据然后利用chipscop进行数据的抓取。
数据发送如下图所示:
对上述代码测试时抓取到数据波形如下图:
从图中可以看出接收了一包完整的标准数据帧。在通过CAN调试工具进行数据的发送测试时:CAN调试工具每秒发送60包,测试了一个小时,没有出现接收数据错误。
然而在实现CAN通信时在满足一般的代码编写的情况下有两点需要特别的注意:
1、CAN2.0的协议规定,连续5个显性/隐性电平后,要填充一位隐性/显性电平。
2、在can协议中将CAN_H和CAN_L的差值为高电平时定义为显性,逻辑上表示为0,为低电平时定义为隐形,逻辑上表示为1。
(有需要代码的可以复制)