Modbus通信协议的FPGA实现

 FPGA 实现 Modbus 通信主要用于高速、并行处理场景,能够实现实时的数据交换和低延迟的响应。

1. Modbus 协议基础

Modbus 协议一般有两个主要版本:

  • Modbus RTU:基于串行通信(RS-232/RS-485)。它使用二进制格式传输数据,通常适用于嵌入式系统中。
  • Modbus TCP:基于以太网通信,采用 TCP/IP 协议栈进行数据传输。

Modbus RTU 帧格式

  • 设备地址(1 字节):唯一标识 Modbus 网络中的设备。
  • 功能码(1 字节):定义主机请求的操作,如读/写寄存器。查询寄存器上的数据用 03,修改寄存器的值就用 06
  • 数据域(可变):具体的操作数据,如寄存器地址和数量。
  • CRC 校验(2 字节):用于检测传输过程中的错误。

FPGA 主要用于 Modbus RTU 的实现,因为其串行通信(RS-232 或 RS-485)特别适合在 FPGA 上使用硬件模块来处理。

Modbus是主从方式通信,不能同步进行通信,总线上每次只有一个数据进行传输。

功能码06示例:

主机发送: 01 06 0001 0007 99c8
从机回复: 01 06 0001 0007 99c8

分别为:地址、功能码、修改0001寄存器的值为0007、CRC

回复一致则写入正确

同时reg_03_01_o的值变为7

功能码03示例:

主机发送 

01_03_0001_0001_d5ca

分别为:从机地址、03功能码、读取寄存器地址、读取的数据个数、CRC校验码

从机回复

 01_03_01_0007_0986

分别为:地址、功能码、读取的数据个数、寄存器值为0007(通过06功能码写入的)、CRC

功能码04示例:

主机发送: 01 04 0001 0004 a009

地址_功能码_起始寄存器地址_读4个_crc

从机回复: 01 04 04 5347 7414 2021 0402 e15c

地址_功能码_数据个数_4个读出值_crc

读出值分别为read_04_01、read_04_02、read_04_03、read_04_04的值

主机发送: 01 04 0001 0003 e1cb
从机回复: 01 04 03 5347 7414 2021 0fd3

读出值分别为read_04_01、read_04_02、read_04_03的值

主机发送: 01 04 0002 0001 900a
从机回复: 01 04 01 7414 6e3f

读出read_04_02的值

主机发送: ff 04 0002 0001 85d4
从机回复: ff 04 01 7414 47eb

读出read_04_02的值

主机发送: 01 04 0003 0003 400b
从机回复:01 04 03 2021 0402 7721 c9ec

读出read_04_03 、read_04_04 、read_04_05的值

主机发送: 01 04 0009 0005 e00b
从机回复: 01 84 03 01 00

09+05 >所有的寄存器数量,04 功能码下的异常,读取数据个数不对,illegal quantity return 03

功能码0x10:修改连续多个寄存器

主机发送起始地址+寄存器个数+总字节数+数据,从机返回起始地址+寄存器数量

2. Modbus 在 FPGA 上的实现

1.uart_byte_tx

`timescale 1ns / 1ns
`define UD #1

module uart_byte_tx #
(
    parameter       CLK_FREQ   = 'd50000000,
    parameter       BAUD_RATE  = 'd9600    
)
(
    input           clk     ,	
    input           rst_n   ,	

    input           tx_start,   // start transfer with pos edge
    input   [7:0]	tx_data ,	// data need to transfer

    output  reg     tx_state,   // sending duration
    output	reg     tx_done ,   // pos-pulse for 1 tick indicates 1 byte transfer done
    output	reg		rs232_tx	// uart transfer pin
);


localparam START_BIT = 1'b0;
localparam STOP_BIT = 1'b1;
localparam BPS_PARAM = CLK_FREQ/BAUD_RATE;

//start 8bit_data transfer operation
reg tx_start_r0;
reg tx_start_r1;
wire tx_start_pos;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
        tx_start_r0 <= `UD 1'b0;
        tx_start_r1 <= `UD 1'b0;
    end
    else
    begin
        tx_start_r0 <= `UD tx_start;
        tx_start_r1 <= `UD tx_start_r0;
    end
end
assign tx_start_pos = ~tx_start_r1 & tx_start_r0;

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
	    tx_state <= `UD 1'b0;
    end
    else 
    begin
        if(tx_start_pos)
        begin
	        tx_state <= `UD 1'b1;
        end
        else if(tx_done)
        begin
	        tx_state <= `UD 1'b0;
        end
        else
        begin
	        tx_state <= `UD tx_state;
        end
    end
end

reg [15:0]  baud_rate_cnt;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
    begin
		baud_rate_cnt <= `UD 16'd0;
    end
	else 
    begin
        if(tx_state)
        begin
		    if(baud_rate_cnt >= BPS_PARAM - 1)
            begin
			    baud_rate_cnt <= `UD 16'd0;
            end
		    else
            begin
			    baud_rate_cnt <= `UD baud_rate_cnt + 1'b1;
            end
	    end
	    else
        begin
		    baud_rate_cnt <= `UD 16'd0;
        end
    end
end

// generate bps_clk signal
reg bps_clk;
always @ (posedge clk or negedge rst_n)
begin
	if(!rst_n) 
    begin
		bps_clk <= `UD 1'b0;
    end
	else
    begin
        if(baud_rate_cnt == 16'd1 )
        begin
		    bps_clk <= `UD 1'b1;	
        end
	    else 
        begin
		    bps_clk <= `UD 1'b0;
        end
    end
end

//bps counter
reg [3:0] bps_cnt;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)	
    begin
	    bps_cnt <= `UD 4'd0;
    end
    else
    begin
        if(bps_cnt == 4'd11)
        begin
	        bps_cnt <= `UD 4'd0;
        end
        else if(bps_clk)
        begin
	        bps_cnt <= `UD bps_cnt + 1'b1;
        end
        else
        begin
	        bps_cnt <= `UD bps_cnt;
        end
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
	    tx_done <= `UD 1'b0;
    end
    else if(bps_cnt == 4'd11)
    begin
	    tx_done <= `UD 1'b1;
    end
    else
    begin
	    tx_done <= `UD 1'b0;
    end
end

reg [7:0] tx_data_r;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
	    tx_data_r <= `UD 8'b0;
    end
    else
    begin
        if(tx_start_pos)
        begin
	        tx_data_r <= `UD tx_data;
        end
        else
        begin
	        tx_data_r <= `UD tx_data_r;
        end
    end
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
	    rs232_tx <= `UD 1'b1;
    end
    else begin
	    case(bps_cnt)
		    0:rs232_tx  <= `UD 1'b1;            // idle hi

		    1:rs232_tx  <= `UD START_BIT;       // start bit lo
		    2:rs232_tx  <= `UD tx_data_r[0];    // LSB first
		    3:rs232_tx  <= `UD tx_data_r[1];    //
		    4:rs232_tx  <= `UD tx_data_r[2];    //
		    5:rs232_tx  <= `UD tx_data_r[3];    //
		    6:rs232_tx  <= `UD tx_data_r[4];    //
		    7:rs232_tx  <= `UD tx_data_r[5];    //
		    8:rs232_tx  <= `UD tx_data_r[6];    //
		    9:rs232_tx  <= `UD tx_data_r[7];    // MSB last
                                                // No parity
		    10:rs232_tx <= `UD STOP_BIT;        // stop bit hi

		    default:rs232_tx <= `UD 1'b1;       // idle hi
	    endcase
    end
end	

endmodule

2.1.uart_byte_rx

`timescale 1ns / 1ns
`define UD #1

module uart_byte_rx #
(
    parameter           CLK_FREQ   = 'd50000000,
    parameter           BAUD_RATE  = 'd9600    
)
(
    input               clk     ,	
    input               rst_n   ,	

    output  reg [7:0]   rx_data ,		// data need to transfer
    output  reg         rx_state,       // recieving duration
    output	reg         rx_done ,       // pos-pulse for 1 tick indicates 1 byte transfer done
    input		        rs232_rx		// uart transfer pin
);

localparam BPS_PARAM = (CLK_FREQ/BAUD_RATE)>>4; // oversample by x16

// sample sum registers, sum of 6 samples
reg [2:0] rx_data_r [0:7];
reg [2:0] START_BIT;
reg [2:0] STOP_BIT;

reg [7:0] bps_cnt;

reg	rs232_rx0,rs232_rx1,rs232_rx2;	
//Detect negedge of rs232_rx
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		rs232_rx0 <= `UD 1'b0;
		rs232_rx1 <= `UD 1'b0;
		rs232_rx2 <= `UD 1'b0;
	end else begin
		rs232_rx0 <= `UD rs232_rx;
		rs232_rx1 <= `UD rs232_rx0;
		rs232_rx2 <= `UD rs232_rx1;
	end
end
wire	neg_rs232_rx;
assign  neg_rs232_rx = rs232_rx2 & ~rs232_rx1;	

always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
    begin
		rx_state <= `UD 1'b0;
    end
	else
    begin
        if(neg_rs232_rx)
        begin
            rx_state <= `UD 1'b1;
        end
	    else if(rx_done || (bps_cnt == 8'd12 && (START_BIT > 2))) // at least 3 of 6 samples are 1, START_BIT not ok
        begin
		    rx_state <= `UD 1'b0;
        end
	    else
        begin
		    rx_state <= `UD rx_state;
        end
    end
end

reg [15:0]  baud_rate_cnt;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
    begin
        baud_rate_cnt <= `UD 16'd0;
    end
    else
    begin
        if(rx_state)
        begin
            if(baud_rate_cnt >= BPS_PARAM - 1)
            begin
                baud_rate_cnt <= `UD 16'd0;
            end
            else
            begin
                baud_rate_cnt <= `UD baud_rate_cnt + 1'b1;
            end
        end
        else
        begin
            baud_rate_cnt <= `UD 16'd0;
        end
    end
end
    
// generate bps_clk signal
reg bps_clk;
always @ (posedge clk or negedge rst_n)
begin
	if(!rst_n) 
    begin
		bps_clk <= `UD 1'b0;
    end
	else
    begin 
        if(baud_rate_cnt == BPS_PARAM>>1 )//sample in cnt center
        begin
		    bps_clk <= `UD 1'b1;	
        end
	    else 
        begin
		    bps_clk <= `UD 1'b0;
        end
    end
end

//bps counter

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)	
    begin
	    bps_cnt <= `UD 8'd0;
    end
    else
    begin
        if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2))) 
        begin
	        bps_cnt <= `UD 8'd0;
        end
        else if(baud_rate_cnt >= BPS_PARAM - 1'b1 )
        begin
	        bps_cnt <= `UD bps_cnt + 1'b1;
        end
        else
        begin
	        bps_cnt <= `UD bps_cnt;
        end
    end
end

always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
    begin
		rx_done <= `UD 1'b0;
    end
	else
    begin
        if(bps_cnt == 8'd159)
        begin
		    rx_done <= `UD 1'b1;
        end
	    else
        begin
		    rx_done <= `UD 1'b0;
        end
    end
end


		
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
    begin
		START_BIT    <= `UD 3'd0;
		rx_data_r[0] <= `UD 3'd0;
		rx_data_r[1] <= `UD 3'd0;
		rx_data_r[2] <= `UD 3'd0;
		rx_data_r[3] <= `UD 3'd0;
		rx_data_r[4] <= `UD 3'd0;
		rx_data_r[5] <= `UD 3'd0;
		rx_data_r[6] <= `UD 3'd0;
		rx_data_r[7] <= `UD 3'd0;
		STOP_BIT     <= `UD 3'd0;
	end
	else
    begin
        if(bps_clk)
        begin
		    case(bps_cnt)
			0:
            begin
				START_BIT    <= `UD 3'd0;
				rx_data_r[0] <= `UD 3'd0;
				rx_data_r[1] <= `UD 3'd0;
				rx_data_r[2] <= `UD 3'd0;
				rx_data_r[3] <= `UD 3'd0;
				rx_data_r[4] <= `UD 3'd0;
				rx_data_r[5] <= `UD 3'd0;
				rx_data_r[6] <= `UD 3'd0;
				rx_data_r[7] <= `UD 3'd0;
				STOP_BIT     <= `UD 3'd0;			
            end
            // 160 bps_cnt from 0 to 159 in a byte, each bit has 16 bps_clk (0~15), 
            // sample form 6 to 11 is the middle 6 of 16 bps_clk
			6,7,8,9,10,11:
            begin
                START_BIT <= `UD START_BIT + rs232_rx2;
            end
			22,23,24,25,26,27:
            begin
                rx_data_r[0] <= `UD rx_data_r[0] + rs232_rx2;
            end
			38,39,40,41,42,43:
            begin
                rx_data_r[1] <= `UD rx_data_r[1] + rs232_rx2;
            end
			54,55,56,57,58,59:
            begin
                rx_data_r[2] <= `UD rx_data_r[2] + rs232_rx2;
            end
			70,71,72,73,74,75:
            begin
                rx_data_r[3] <= `UD rx_data_r[3] + rs232_rx2;
            end
			86,87,88,89,90,91:
            begin
                rx_data_r[4] <= `UD rx_data_r[4] + rs232_rx2;
            end
			102,103,104,105,106,107:
            begin
                rx_data_r[5] <= `UD rx_data_r[5] + rs232_rx2;
            end
			118,119,120,121,122,123:
            begin
                rx_data_r[6] <= `UD rx_data_r[6] + rs232_rx2;
            end
			134,135,136,137,138,139:
            begin
                rx_data_r[7] <= `UD rx_data_r[7] + rs232_rx2;
            end
			150,151,152,153,154,155:
            begin
                STOP_BIT <= `UD STOP_BIT + rs232_rx2;
            end
			default:
			begin
				START_BIT <= `UD START_BIT;
				rx_data_r[0] <= `UD rx_data_r[0];
				rx_data_r[1] <= `UD rx_data_r[1];
				rx_data_r[2] <= `UD rx_data_r[2];
				rx_data_r[3] <= `UD rx_data_r[3];
				rx_data_r[4] <= `UD rx_data_r[4];
				rx_data_r[5] <= `UD rx_data_r[5];
				rx_data_r[6] <= `UD rx_data_r[6];
				rx_data_r[7] <= `UD rx_data_r[7];
				STOP_BIT <= `UD STOP_BIT;						
			end
		    endcase
        end
	end
end

always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
    begin
		rx_data <= `UD 8'd0;
    end
	else
    begin
        if(bps_cnt == 8'd159)
        begin
            // rx_data_r[x] has 3 width, actual sample sum range 0~6, rx_data_r[x][2] means sum/4
            // if sum >=4 sample value == 1, else sum < 4 sample value == 0
		    rx_data[0] <= `UD rx_data_r[0][2];
		    rx_data[1] <= `UD rx_data_r[1][2];
		    rx_data[2] <= `UD rx_data_r[2][2];
		    rx_data[3] <= `UD rx_data_r[3][2];
		    rx_data[4] <= `UD rx_data_r[4][2];
		    rx_data[5] <= `UD rx_data_r[5][2];
		    rx_data[6] <= `UD rx_data_r[6][2];
		    rx_data[7] <= `UD rx_data_r[7][2];
	    end
    end
end


endmodule

3.ct_15_gen

`timescale 1ns / 1ns
`define UD #1

module ct_15t_gen #
(
    parameter           CLK_FREQ   = 'd50000000,// 50MHz
    parameter           BAUD_RATE  = 'd9600    
)
(
    input     clk         , 
    input     rst_n       , 

    input     rx_done     ,  //表示接收端接收到一个完整字节的信号,触发一次有效脉冲
    input     rx_state    ,  //表示当前处于接收状态
						     
    output    rx_drop_frame  // if intervel >1.5T == 1,表明丢帧情况
);

localparam BPS_PARAM = (CLK_FREQ/BAUD_RATE); //计算得到的分频系数,表示每位传输所需的时钟周期数  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值