串口RS485

发布时间 2023-08-27 17:58:19作者: jasonlee~

第31章 、串口RS485

【理论】

【RS485简介】

1、RS-485是双向、半双工通信协议,允许多个驱动器和接收器挂接在 总线 上,其中每个驱动器都能够脱离总线。(RS232为双向,双工,单端传输)

半双工:指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。

2、RS-485采用 差分传输 方式能有效抑制干扰。

单端传输:指在传输过程中,在一根导线上传输 对地之间的电平差,用这个电平差值来表示逻辑“0”和“1”(RS232为单端传输方式)。

差分传输:使用两根线进行传输信号,这两根线上的信号 振幅相等,相位相差180度,极性相反。在这两根线上传输的信号就是差分信号,信号接收端比较这两个信号电压的差值来判断发送端发送的逻辑“0”和逻辑“1”。

由上图可以看到,单端传输 发送什么信号就 输出什么信号,而差分传输则是 输出发送端信号的 差值。当有共模干扰(两导线上的干扰电流振幅相等,频率、相位、方向相同)时,差分传输就能有效的抑制干扰,而单端传输却不能。

RS485相较于RS232:抗干扰能力较强,可长距离传输,最大可达上千米,同时RS-485接口在 总线上允许连接多个收发器,可利用单一的RS-485接口方便地建立起 设备网络。RS-485接口芯片广泛应用于工业控制、仪器、仪表、多媒体网络、机电一体化产品等诸多领域。

【实战】

利用RS-485接口,使用 两块FPGA开发板 实现 两块开发板之间的流水灯、呼吸灯控制。使用A开发板上的2个按键控制B板上的LED灯的模式,使用B开发板上的2个按键控制A板上的LED灯的模式;

具体为:当按下A开发板的按键1时,B开发板的流水灯亮,再次按下按键1时,B板流水灯灭;同理呼吸灯也是如此。

需要注意的是开发板中的led只能显示一种状态(流水灯或呼吸灯),所以当显示流水灯/呼吸灯时,按下呼吸灯按键/流水灯按键后,led会显示呼吸灯/流水灯。

【硬件资源】

 

MAX3485 为RS485 收发器芯片,该收发器可实现 差分信号 与 TTL信号 之间的转换。

RS485是 半双工通信,当引脚2为低电平时,接收使能;当引脚3为高电平时,发送使能。将引脚2和引脚3连接在一起形成RS485使能;

当 RS485_RE 为 高(11) 时 发送 过程,为 低(00) 时 接收 过程。

  1. 数据发送(FPGA-AB端口): RS485_RE 拉高,单端信号由 FPGA 传入 MAX3485收发芯片,转换成 差分信号 传出至 AB端口;
  2. 数据接收(AB端口-FPGA):RS485_RE 拉低,差分信号由 AB端口 传入 MAX3485收发芯片,转换成 单端信号 传出至 FPGA;

【注】RS485与RS232使用同样的传输协议,故而数据传输的帧结构相同(10bit,1位起始位,8位数据位,1位停止位)

 

两块板进行RS485通信实验,485B口连接另一块板子的485B口,485A口连接到另一块板子的485A口。其之间的连线如下图:

下图为串口与CAN接口的选择,需使用跳帽将3、5脚(对应开发板J6口上485_T、TX)连接以及将4、6脚(对应开发板J6口上485_R、RX)连接,用于选择RS485接口。

【程序设计】

工程的工作流程:通过 led_ctrl 模块产生控制另一块开发板led灯的控制信号通过uart_tx模块发送到另一块开发板。同时若另外一块开发板传来了led灯的控制信号,控制信号经由uart_rx模块传入,通过led模块来控制开发板的led灯状态。

【按键消抖模块(key_filter)】

module key_filter(
input   wire sys_clk      ,
input   wire sys_rst_n    ,
input   wire key_in   , 
output  reg  key_flag
);  

parameter M_cnt_20ms = 1_000_000 ; //计数20ms,20ms=20_000_000ns,计数个数为:20_000_000ns/20ns = 1_000_000个

reg [19:0]cnt_20ms;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_20ms <= 0;
else if(key_in1'b1)//① 按键一直为高,则未按下,不计数;
cnt_20ms <= 0; //② 按键按下之后为高,则有抖动,计数清零,重新计数
else if((key_in
1'b0) && (cnt_20ms==M_cnt_20ms - 1'b1))//按键为低,但计数满了,保持计数值
cnt_20ms <= cnt_20ms;//即计数值持续等于999_999
else
cnt_20ms <= cnt_20ms + 1'b1;//按键为低,计数未满20ms,计数
end

// 按键消抖成功信号,即key_in保持20ms
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
key_flag <= 0;
else if(cnt_20ms==M_cnt_20ms - 2'd2)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
end

endmodule

`timescale  1ns/1ns

module  tb_key_filter();

//parameter define
parameter   CNT_1MS  = 20'd19   , //为了缩短仿真时间,将参数化的时间值改小,但位宽依然定义和参数名的值保持一致,也可以将这些参数值改成和参数名的值一致
            CNT_11MS = 21'd69   ,
            CNT_41MS = 22'd149  ,
            CNT_51MS = 22'd199  ,
            CNT_60MS = 22'd249  ;//完整按键按下到释放过程,前抖动10ms、后抖动10ms、稳定

//wire  define
wire            key_flag        ;   //消抖后按键信号

//reg   define
reg             sys_clk         ;   
reg             sys_rst_n       ;   
reg             key_in          ;   //模拟按键输入
reg     [21:0]  tb_cnt          ;   //模拟按键按下整个过程计数器,前抖动10ms、稳定20ms、后抖动10ms,共40ms,计数个数:40_000_000/20=2_000_000个

initial begin
    sys_clk    = 1'b1;
    sys_rst_n  = 1'b0;
    // key_in     = 1'b1;  //初始化按键输入
    #20         
    sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;

//tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tb_cnt <= 22'b0;
    else if(tb_cnt == CNT_60MS)//计数器计数到CNT_60MS完成一次按键从按下到释放的整个过程,51-60ms为按键空闲时间
        tb_cnt <= 22'b0;
    else
        tb_cnt <= tb_cnt + 1'b1;

//key_in:产生按键输入随机数,模拟按键输入
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_in <= 1'b1;     //按键未按下时的状态为为高电平
    else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS) || (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
        key_in <= {$random} % 2;     //在60ms的前10ms和后10ms,产生非负随机数0、1,模拟按键抖动
    else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
        key_in <= 1'b0;  //按键经过10ms的前抖动后稳定在低电平,持续时间需大于30ms,此处仿真设置稳定时间为30ms
    else
        key_in <= 1'b1;

key_filter #(.M_cnt_20ms(20'd50)) key_filter_inst01
(
    .sys_clk    (sys_clk    ),  
    .sys_rst_n  (sys_rst_n  ),  
    .key_in     (key_in     ),  
                        
    .key_flag   (key_flag   )   
);

endmodule

【呼吸灯模块】

module  breath_led
#(
    parameter CNT_1US_MAX = 6'd49   ,
    parameter CNT_1MS_MAX = 10'd999 ,
    parameter CNT_1S_MAX  = 10'd999
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
output  reg     led_out         //输出信号,控制led灯

);

//reg define
reg [5:0] cnt_1us ; //计数50个,6位
reg [9:0] cnt_1ms ; //计数1000个,10位
reg [9:0] cnt_1s ;
reg cnt_1s_flag ;

//cnt_1us:1us计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1us <= 6'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1us <= 6'b0;
else
cnt_1us <= cnt_1us + 1'b1;

//cnt_1ms:1ms计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1ms <= 10'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms + 1'b1;

//cnt_1s:1s计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1s <= 10'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1s <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1s <= cnt_1s + 1'b1;

//cnt_1s_flag:1s计数器标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1s_flag <= 1'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1s_flag <= ~cnt_1s_flag; //1s计数完,转换led灯亮灭模式

//led_out:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if((cnt_1s_flag == 1'b1 && cnt_1ms < cnt_1s) //找led为低时的波形
|| (cnt_1s_flag == 1'b0 && cnt_1ms > cnt_1s))
led_out <= 1'b0;
else
led_out <= 1'b1;

endmodule

【流水灯模块】

module  water_led(
    input   wire          sys_clk     ,   //系统时钟50Mh
    input   wire          sys_rst_n   ,  //全局复位
output  wire   [3:0]  led_out      //输出控制led灯

);

parameter CNT_MAX = 25'd25_000_000 ;

//reg define
reg [24:0] water_cnt ;
reg cnt_flag ;
reg [3:0] led_out_reg ;

//cnt:计数器计数1s
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
water_cnt <= 25'b0;
else if(water_cnt == CNT_MAX -1'b1 )
water_cnt <= 25'b0;
else
water_cnt <= water_cnt + 1'b1;
end
//cnt_flag:计数器计数满1s标志信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(water_cnt == CNT_MAX - 2'd2)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
end

//led_out_reg:led循环流水
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out_reg <= 4'b0001;
else if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
led_out_reg <= 4'b0001;
else if(cnt_flag == 1'b1)
led_out_reg <= led_out_reg << 1'b1; //左移

assign led_out = ~led_out_reg;

// always@(posedge sys_clk or negedge sys_rst_n)begin
// if(sys_rst_n == 1'b0)
// led_out <= 4'b0000;
// else if(led_out == 4'b0111 && cnt_flag == 1'b1)
// led_out <= 4'b1110;
// else if(cnt_flag == 1'b1)
// led_out <= led_out << 1'b0;
// // led_out <= {led_out[3:1],1'b1} ;
// // led_out <= led_out << 1'b1; //左移
// else
// led_out <= led_out ;
// end

endmodule

【串口接收模块(uart_rx)】

同《RS232》章节中的uart_rx模块

module uart_rx(
    input            sys_clk    ,
    input            sys_rst_n  ,
    input            din        ,
output reg [7:0] po_data    ,
output reg       po_flag  

);

parameter UART_BPS = 'd9600 ,//数据传输波特率 9600
CLK_FREQ = 'd50_000_000 ;//系统时钟频率 50MHZ

localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS ;

reg din_reg1 ;
reg din_reg2 ;
reg din_reg3 ;
reg [3:0] bit_cnt ;
wire bit_flag ;

//将接收的rx信号打两拍,消除亚稳态。再打一拍用消除亚稳态后的din_reg2、din_reg3进行下降沿判断
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
din_reg1 <= 1'b1 ;
else
din_reg1 <= din ;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
din_reg2 <= 1'b1 ;
else
din_reg2 <= din_reg1 ;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
din_reg3 <= 1'b1 ;
else
din_reg3 <= din_reg2 ;
end

//找到起始位下降沿,数据开始传输
reg nedge_flag ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
nedge_flag <= 1'b0 ;
else if((din_reg31'b1)&&(din_reg21'b0))
nedge_flag <= 1'b1 ;
else
nedge_flag <= 1'b0 ;
end

//rx_en:接收使能信号
reg rx_en ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_en <= 1'b0 ;
else if(nedge_flag1'b1)
rx_en <= 1'b1 ;
else if((bit_cnt
4'd9)&&(bit_flag))
rx_en <= 1'b0 ;
else
rx_en <= rx_en ;
end

//波特率 9600,1s(10^9ns)传输数据 (9600*1)bit,时钟周期 20ns,传输1bit数据需要ns:(10^9ns)/9600
// --> 传输1bit数据需要时钟周期个数:((10^9ns)/9600)/20ns = 5208个,计数 0-5297
reg [12:0] baud_cnt ;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
baud_cnt <= 13'd0 ;
else if((rx_en==1'b1) && (baud_cnt == (BAUD_CNT_MAX - 1'b1)))
baud_cnt <= 13'd0 ;
else if(rx_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1 ;
else
baud_cnt <= 13'd0 ;
end

assign bit_flag = (baud_cnt == ((BAUD_CNT_MAX/2)-1)) ;//在数据传输中间点赋值,更加准确

always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
bit_cnt <= 4'b0 ;
else if((bit_cnt4'd9)&&(bit_flag1'b1))
bit_cnt <= 4'd0 ;
else if( bit_flag==1'b1 )
bit_cnt <= bit_cnt + 1'b1 ;
else
bit_cnt <= bit_cnt ;
end

//串行转并行,移位
reg [7:0] rx_data ;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_data <= 8'd0 ;
else if((bit_cnt > 1'b1) && (bit_flag == 1'b1))
rx_data <= {din_reg3,rx_data[7:1]};
end
//移位完成标志信号
reg rx_flag ;
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
rx_flag <= 1'b0 ;
else if(rx_en==1'b1)
rx_flag <= 1'b1 ;
else rx_flag <= 1'b0 ;
end

//输出信号,8bit并行数据
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
po_data <= 8'd0 ;
else if(rx_flag)
po_data <= rx_data ;
else
po_data <= 8'b0 ;
end
//输出完成标志信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
po_flag <= 1'b0 ;
else
po_flag <= rx_flag ;
end

endmodule

【串口发送模块(uart_tx)】

与《RS232》章节中的uart_tx模块大致相同,唯一不同的是 work_en(发送工作使能信号):

  1. 需要将 work_en(发送工作使能信号)作为输出连接到 MAX3485收发器 中去 控制信息的 发送和接收。
  2. 需要将 work_en(发送工作使能信号)拉高至 停止位:

【注】《RS232》章节中的uart_tx模块的 work_en信号是 拉高到 数据位的最后一位,停止位并没有拉高。通过测试发现这会导致开发板在向另一块开发板发送信息时,rx在没有接受到数据时会拉低几个时钟,而uart_rx模块是通过rx的下降沿来判断是否传来数据的,这样就会导致开发板误以为另一块开发板发送过来了信息,导致结果出现错误。

module  uart_tx
#(
    parameter   UART_BPS    =   'd9600,           //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000      //时钟频率
)
(
    input   wire            sys_clk     ,   //系统时钟50Mhz
    input   wire            sys_rst_n   ,   //全局复位
    input   wire    [7:0]   pi_data     ,   //并行数据
    input   wire            pi_flag     ,   //并行数据有效标志信号
output  reg             work_en     ,   //发送使能,高有效
output  reg             tx              //串口发送数据

);

//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;

//reg define
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(pi_flag == 1'b1)
work_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到5207
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if( /* baud_cnt == 13'd1 */baud_cnt == BAUD_CNT_MAX - 1 )
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (work_en == 1'b1))
bit_cnt <= bit_cnt + 1'b1;

//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx <= 1'b1; //空闲状态时为高电平
else if(/* bit_flag == 1'b1 */work_en == 1'b1)
case(bit_cnt)
0 : tx <= 1'b0;
1 : tx <= pi_data[0];
2 : tx <= pi_data[1];
3 : tx <= pi_data[2];
4 : tx <= pi_data[3];
5 : tx <= pi_data[4];
6 : tx <= pi_data[5];
7 : tx <= pi_data[6];
8 : tx <= pi_data[7];
9 : tx <= 1'b1;
default : tx <= 1'b1;
endcase

endmodule

【led灯控制模块(led_ctrl)】

该模块需产生发送标志信号(pi_flag),以及产生发送数据(pi_data),以及输出led信号控制led灯显示。

实验任务是用 A板的按键 控制 B板的led灯,需要用 按键信号 产生led灯状态的 控制信号,这个信号在改变led灯状态时需维持有效信号不变。因为有两种led灯状态,所以需产生 两个状态标志信号。

  1. water_key_flag --> water_led_flag:流水灯状态标志信号,高有效。当 流水灯按键信号有效时(water_key_flag=1),拉高 流水灯状态标志 信号,拉低 呼吸灯状态标志信号。当再次按下流水灯按键时,拉低流水灯状态标志信号,使led灯状态变为不显示状态。
  2. breath_key_flag --> breath_led_flag:呼吸灯状态标志信号,高有效。当 呼吸灯按键信号有效(breath_key_flag=1)时,拉低 流水灯状态标志 信号 ,拉高 呼吸灯状态标志信号。当再次按下呼吸灯按键时,拉低呼吸灯状态标志信号,使led灯从显示呼吸灯变为不显示,
  3. pi_data:pi_flag 拉高开始传pi_data的值。所以要将led灯控制信号赋值给pi_data发送出去。两个led灯控制信号(2bit),赋给pi_data的低两位即可。使用赋值语句:assign pi_data = {6’d0,breath_led_flag,water_led_flag} ;
  4. pi_flag:当按下按键时就需要对另一块开发板进行led灯控制,所以可使用 按键有效信号 作为 pi_flag信号,同样也可使用赋值语句:assign pi_flag = water_key_flag | breath_key_flag ;

接收的 po_data 即为发送的pi_flag数据。在上面我们已经讲到我们发送的pi_data值为:{6’d0,breath_led_flag,water_led_flag },所以我们接收的po_data的值也为{6’d0,breath_led_flag,water_led_flag }。在没接收到数据时po_data为8’d0,所以我们只需要对po_data[0],po_data[1]的值进行使用即可。

po_data[0]:即接收的water_led_flag值。

po_data[1]:即接收的breath_led_flag值。

  1. 当接收的 po_data[0] 的值为1:说明另一块开发板传来 流水灯状态标志信号 且有效,此时将led灯状态从 不显示状态 切换到 流水灯状态。
  2. 当接收到 po_data[1] 的值为1:说明另一块开发板传来 呼吸灯状态标志信号 且有效,此时将led灯状态 从不显示状态 切换到显示 呼吸灯状态。
  3. 当po_data[0],po_data[1]为0:说明 不需要 显示 流水灯和呼吸灯,此时我将 led_out 的值赋为 4’b1111(全灭状态)。
module  led_ctrl(
    input   wire            sys_clk         ,   //模块时钟,50MHz 
    input   wire            sys_rst_n       ,   //复位信号,低有效
    input   wire            water_key_flag  ,   //流水灯按键有效信号
    input   wire            breath_key_flag ,   //呼吸灯按键有效信号
    input   wire    [3:0]   led_out_w       ,   //流水灯
    input   wire            led_out_b       ,   //呼吸灯
    input   wire    [7:0]   po_data         ,   //接收数据
output  wire            pi_flag         ,   //发送标志信号
output  wire    [7:0]   pi_data         ,   //发送数据
output  reg     [3:0]   led_out             //输出led灯

);

//reg define
reg water_led_flag ; //流水灯标志信号,作为pi_data[0]发送
reg breath_led_flag ; //呼吸灯标志信号,作为pi_data[1]发送

//按下呼吸灯按键或流水灯按键时,开始发送数据
assign pi_flag = water_key_flag || breath_key_flag ;
//低两位数据为led控制信号
assign pi_data = {6'd0,breath_led_flag,water_led_flag};

//water_key_flag:串口发送的控制信号,高时流水灯,低时停止(按键控制)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
water_led_flag <= 1'b0;
else if(breath_key_flag == 1'b1) //呼吸灯按键按下,切换成呼吸灯模式
water_led_flag <= 1'b0;
else if(water_key_flag == 1'b1)//再次按下流水灯按键,使该状态停止
water_led_flag <= ~water_led_flag;
//breath_key_flag:串口发送的控制信号,高时呼吸灯灯,低时停止(按键控制)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
breath_led_flag <= 1'b0;
else if(water_key_flag == 1'b1)
breath_led_flag <= 1'b0;
else if(breath_key_flag == 1'b1)
breath_led_flag <= ~breath_led_flag;

//led_out:当传入的流水灯有效时,led灯为流水灯,同理呼吸灯也是如此
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 4'b1111;
else if(po_data[0] == 1'b1 )//第0位为1,流水灯模式
led_out <= led_out_w;
else if(po_data[1] == 1'b1 )//第1位为1,流水灯模式
//使四个led灯都显示呼吸灯状态
led_out <= {led_out_b,led_out_b,led_out_b,led_out_b};
else
led_out <= 4'b1111;

endmodule

 

 

 

 

【顶层模块】

`timescale  1ns/1ns
module  top_rs485(
    input   wire            sys_clk     ,   //系统时钟,50MHz
    input   wire            sys_rst_n   ,   //复位信号,低有效
    input   wire            rx          ,   //串口接收数据
    input   wire    [1:0]   key         ,   //两个按键
output  wire            work_en     ,   //发送使能,高有效
output  wire            tx          ,   //串口接收数据
output  wire    [3:0]   led             //led灯

);

//parameter define
parameter UART_BPS = 14'd9600; //比特率
parameter CLK_FREQ = 26'd50_000_000; //时钟频率

//wire define
wire [7:0] po_data ; //接收数据
wire [7:0] pi_data ; //发送数据
wire pi_flag ; //发送标志信号
wire water_key_flag ; //流水灯按键有效信号
wire breath_key_flag ; //呼吸灯按键有效信号
wire [3:0] led_out_w ; //流水灯
wire led_out_b ; //呼吸灯

////
//
Main Code ****************************//
//
*****************************//

//--------------------uart_rx_inst------------------------
uart_rx

(

.UART_BPS    (UART_BPS  ),   //串口波特率
.CLK_FREQ    (CLK_FREQ  )    //时钟频率

)
uart_rx_inst(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.din (rx ), //串口接收数据

.po_data     (po_data   ),   //串转并后的8bit数据
.po_flag     (          )    //接收数据完成标志信号没用到可不接

);

//--------------------uart_tx_inst------------------------
uart_tx

(

.UART_BPS    (UART_BPS  ),   //串口波特率
.CLK_FREQ    (CLK_FREQ  )    //时钟频率

)
uart_tx_inst(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.pi_data (pi_data ), //并行数据
.pi_flag (pi_flag ), //并行数据有效标志信号

.work_en     (work_en   ),   //发送使能,高有效
.tx          (tx        )    //串口发送数据

);

//--------------------key_filter_inst------------------------
//两个按键信号例化两次
key_filter key_filter_w(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.key_in (key[0] ), //按键输入信号

.key_flag       (water_key_flag)  //key_flag为1时表示消抖后按键有效

);
key_filter key_filter_b(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.key_in (key[1] ), //按键输入信号

.key_flag       (breath_key_flag) //key_flag为1时表示消抖后按键有效

);

//--------------------key_ctrl_inst------------------------
led_ctrl led_ctrl_inst(
.sys_clk (sys_clk ), //模块时钟,50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.water_key_flag (water_key_flag ), //流水灯按键有效信号
.breath_key_flag (breath_key_flag), //呼吸灯按键有效信号
.led_out_w (led_out_w ), //流水灯
.led_out_b (led_out_b ), //呼吸灯
.po_data (po_data ), //接收数据

.pi_flag         (pi_flag        ),   //发送标志信号
.pi_data         (pi_data        ),   //发送数据
.led_out         (led            )    //输出led灯

);

//--------------------water_led_inst------------------------
water_led water_led_inst(
.sys_clk (sys_clk ), //系统时钟50Mh
.sys_rst_n (sys_rst_n ), //全局复位

.led_out         (led_out_w )    //输出控制led灯

);

//--------------------breath_led_inst------------------------
breath_led breath_led_inst(
.sys_clk (sys_clk ), //系统时钟50Mhz
.sys_rst_n (sys_rst_n ), //全局复位

.led_out         (led_out_b )    //输出信号,控制led灯

);

endmodule

`timescale  1ns/1ns
module  tb_rs485();
//wire  define
wire            rx1         ;
wire            work_en1    ;
wire            tx1         ;
wire    [3:0]   led1        ;
wire            work_en2    ;
wire            tx2         ;
wire    [3:0]   led2        ;

//reg   define
reg             sys_clk     ;
reg             sys_rst_n   ;
reg     [1:0]   key1        ;
reg     [1:0]   key2        ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//对sys_clk,sys_rst赋初值,并模拟按键抖动
initial
    begin
            sys_clk     =   1'b1 ;
            sys_rst_n   <=  1'b0 ;
            key1        <=  2'b11;
            key2        <=  2'b11;
    #200    sys_rst_n   <=  1'b1 ;
//按下流水灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下呼吸灯按键
    #2000000    key1[1]      <=  1'b0;//按下按键
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #200        key1[1]      <=  1'b1;//松开按键
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
    #20         key1[1]      <=  1'b0;//模拟抖动
    #20         key1[1]      <=  1'b1;//模拟抖动
//按下流水灯灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
//按下流水灯灯按键
    #2000000    key1[0]      <=  1'b0;//按下按键
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #200        key1[0]      <=  1'b1;//松开按键
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    #20         key1[0]      <=  1'b0;//模拟抖动
    #20         key1[0]      <=  1'b1;//模拟抖动
    end

//sys_clk:模拟系统时钟,每10ns电平取反一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk;

// //重新定义参数值,缩短仿真时间仿真
// //发送板参数
// defparam    top_rs485_inst1.key_filter_w.CNT_MAX         =   5      ;
// defparam    top_rs485_inst1.key_filter_b.CNT_MAX         =   5      ;
// defparam    top_rs485_inst1.uart_rx_inst.UART_BPS        =   1000000;
// defparam    top_rs485_inst1.uart_tx_inst.UART_BPS        =   1000000;
// defparam    top_rs485_inst1.water_led_inst.CNT_MAX       =   4000   ;
// defparam    top_rs485_inst1.breath_led_inst.CNT_1US_MAX  =   4      ;
// defparam    top_rs485_inst1.breath_led_inst.CNT_1MS_MAX  =   9      ;
// defparam    top_rs485_inst1.breath_led_inst.CNT_1S_MAX   =   9      ;
// //接收板参数
// defparam    top_rs485_inst2.key_filter_w.CNT_MAX         =   5      ;
// defparam    top_rs485_inst2.key_filter_b.CNT_MAX         =   5      ;
// defparam    top_rs485_inst2.uart_rx_inst.UART_BPS        =   1000000;
// defparam    top_rs485_inst2.uart_tx_inst.UART_BPS        =   1000000;
// defparam    top_rs485_inst2.water_led_inst.CNT_MAX       =   4000   ;
// defparam    top_rs485_inst2.breath_led_inst.CNT_1US_MAX  =   4      ;
// defparam    top_rs485_inst2.breath_led_inst.CNT_1MS_MAX  =   99     ;
// defparam    top_rs485_inst2.breath_led_inst.CNT_1S_MAX   =   99     ;

//发送板
//-------------top_rs485_inst1-------------
top_rs485  top_rs485_inst1
(
    .sys_clk     (sys_clk    ),   //系统时钟,50MHz
    .sys_rst_n   (sys_rst_n  ),   //复位信号,低有效
    .rx          (rx1        ),   //串口接收数据
    .key         (key1       ),   //两个按键

    .work_en     (work_en1   ),   //发送使能,高有效
    .tx          (tx1        ),   //串口发送数据
    .led         (led_tx1    )    //led灯

);

//接收板
//-------------top_rs485_inst2-------------
top_rs485   top_rs485_inst2
(
    .sys_clk     (sys_clk    ),   //系统时钟,50MHz
    .sys_rst_n   (sys_rst_n  ),   //复位信号,低有效
    .rx          (tx1        ),   //串口接收数据
    .key         (key2       ),   //两个按键

    .work_en     (work_en2   ),   //发送使能,高有效
    .tx          (tx2        ),   //串口发送数据
    .led         (led_rx2    )    //led灯

);
endmodule