串口RS485

第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_in1'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;


output  reg     led_out         //输出信号,控制led灯
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


output  wire   [3:0]  led_out      //输出控制led灯
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_cnt4'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


output reg [7:0] po_data    ,
output reg       po_flag  
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


output  reg             work_en     ,   //发送使能,高有效
output  reg             tx              //串口发送数据
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;


output  wire            pi_flag         ,   //发送标志信号
output  wire    [7:0]   pi_data         ,   //发送数据
output  reg     [3:0]   led_out             //输出led灯
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灯




);


output  wire            work_en     ,   //发送使能,高有效
output  wire            tx          ,   //串口接收数据
output  wire    [3:0]   led             //led灯
.UART_BPS    (UART_BPS  ),   //串口波特率
.CLK_FREQ    (CLK_FREQ  )    //时钟频率
.po_data     (po_data   ),   //串转并后的8bit数据
.po_flag     (          )    //接收数据完成标志信号没用到可不接
.UART_BPS    (UART_BPS  ),   //串口波特率
.CLK_FREQ    (CLK_FREQ  )    //时钟频率
.work_en     (work_en   ),   //发送使能,高有效
.tx          (tx        )    //串口发送数据
.key_flag       (water_key_flag)  //key_flag为1时表示消抖后按键有效
.key_flag       (breath_key_flag) //key_flag为1时表示消抖后按键有效
.pi_flag         (pi_flag        ),   //发送标志信号
.pi_data         (pi_data        ),   //发送数据
.led_out         (led            )    //输出led灯
.led_out         (led_out_w )    //输出控制led灯
.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

posted @   jasonlee~  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示