串口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) 时 接收 过程。
- 数据发送(FPGA-AB端口): RS485_RE 拉高,单端信号由 FPGA 传入 MAX3485收发芯片,转换成 差分信号 传出至 AB端口;
- 数据接收(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;
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 ;//系统时钟频率 50MHZlocalparam 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 ;
endassign 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(发送工作使能信号):
- 需要将 work_en(发送工作使能信号)作为输出连接到 MAX3485收发器 中去 控制信息的 发送和接收。
- 需要将 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灯状态,所以需产生 两个状态标志信号。
- water_key_flag --> water_led_flag:流水灯状态标志信号,高有效。当 流水灯按键信号有效时(water_key_flag=1),拉高 流水灯状态标志 信号,拉低 呼吸灯状态标志信号。当再次按下流水灯按键时,拉低流水灯状态标志信号,使led灯状态变为不显示状态。
- breath_key_flag --> breath_led_flag:呼吸灯状态标志信号,高有效。当 呼吸灯按键信号有效(breath_key_flag=1)时,拉低 流水灯状态标志 信号 ,拉高 呼吸灯状态标志信号。当再次按下呼吸灯按键时,拉低呼吸灯状态标志信号,使led灯从显示呼吸灯变为不显示,
- pi_data:pi_flag 拉高开始传pi_data的值。所以要将led灯控制信号赋值给pi_data发送出去。两个led灯控制信号(2bit),赋给pi_data的低两位即可。使用赋值语句:assign pi_data = {6’d0,breath_led_flag,water_led_flag} ;
- 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值。
- 当接收的 po_data[0] 的值为1:说明另一块开发板传来 流水灯状态标志信号 且有效,此时将led灯状态从 不显示状态 切换到 流水灯状态。
- 当接收到 po_data[1] 的值为1:说明另一块开发板传来 呼吸灯状态标志信号 且有效,此时将led灯状态 从不显示状态 切换到显示 呼吸灯状态。
- 当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