文章目录

  • ​​第31讲:串口RS232​​
  • ​​串口数据接收模块:Uart_rx​​
  • ​​串口数据发送模块:Uart_tx​​
  • ​​底层模块:rs232​​
  • ​​第32讲:使用SignalTap II嵌入式逻辑分析仪在线调试​​
  • ​​第33讲:串口RS485​​
  • ​​key_filter​​
  • ​​water_led​​
  • ​​breath_led​​
  • ​​led_ctrl​​
  • ​​uart_rx​​
  • ​​uart_tx​​
  • ​​rs485​​
  • ​​tb_rs485​​

第31讲:串口RS232

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,简称UART)
UART是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
包括RS232、RS499、RS423、RS422和RS485等接口标准规范和总线标准规范。

野火FPGA强化(1):串口_数据


野火FPGA强化(1):串口_串口_02


野火FPGA强化(1):串口_fpga开发_03


野火FPGA强化(1):串口_串口_04



实验目标

野火FPGA强化(1):串口_fpga开发_05


顶层模块设计

野火FPGA强化(1):串口_fpga开发_06


子功能模块设计

野火FPGA强化(1):串口_fpga开发_07


系统框图

野火FPGA强化(1):串口_串口_08

串口数据接收模块:Uart_rx

串口数据接收模块

野火FPGA强化(1):串口_串口_09

`timescale  1ns/1ns

module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据

output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);

//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;

//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;

//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;

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

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
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计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;

endmodule

tb_uart_rx.v

`timescale  1ns/1ns
module tb_uart_rx();

//reg define
reg sys_clk;
reg sys_rst_n;
reg rx;

//wire define
wire [7:0] po_data;
wire po_flag;

//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end

//模拟发送8次数据,分别为0~7
initial begin
#200
rx_bit(8'd0); //任务的调用,任务名+括号中要传递进任务的参数
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i; //定义一个常量
//用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1
//不可以写成C语言i=i++的形式
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask //任务以endtask结束

//------------------------uart_rx_inst------------------------
uart_rx uart_rx_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.po_data (po_data ), //output [7:0] po_data
.po_flag (po_flag ) //output po_flag
);

endmodule

串口数据发送模块:Uart_tx

野火FPGA强化(1):串口_数据_10


野火FPGA强化(1):串口_串口_11

`timescale  1ns/1ns

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 , //模块输入的8bit数据
input wire pi_flag , //并行数据有效标志信号

output reg tx //串转并后的1bit数据
);

//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 ;
reg work_en ;

//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计数到BAUD_CNT_MAX - 1
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)
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)
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

tb_uart_tx.v

`timescale  1ns/1ns
module tb_uart_tx();

//reg define
reg sys_clk;
reg sys_rst_n;
reg [7:0] pi_data;
reg pi_flag;

//wire define
wire tx;

//初始化系统时钟、全局复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20;
sys_rst_n <= 1'b1;
end

//模拟发送7次数据,分别为0~7
initial begin
pi_data <= 8'b0;
pi_flag <= 1'b0;
#200
//发送数据0
pi_data <= 8'd0;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
//每发送1bit数据需要5208个时钟周期,一帧数据为10bit
//所以需要数据延时(5208*20*10)后再产生下一个数据
#(5208*20*10);
//发送数据1
pi_data <= 8'd1;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据2
pi_data <= 8'd2;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据3
pi_data <= 8'd3;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据4
pi_data <= 8'd4;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据5
pi_data <= 8'd5;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据6
pi_data <= 8'd6;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*20*10);
//发送数据7
pi_data <= 8'd7;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//------------------------uart_rx_inst------------------------
uart_tx uart_tx_inst(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_data (pi_data ), //output [7:0] pi_data
.pi_flag (pi_flag ), //output pi_flag

.tx (tx ) //input tx
);

endmodule

底层模块:rs232

`timescale  1ns/1ns

module rs232
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据

output wire tx //串口发送数据
);

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

//wire define
wire [7:0] po_data;
wire po_flag;

//------------------------ uart_rx_inst ------------------------
uart_rx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_rx_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.po_data (po_data ), //output [7:0] po_data
.po_flag (po_flag ) //output po_flag
);

//------------------------ uart_tx_inst ------------------------
uart_tx
#(
.UART_BPS (UART_BPS ), //串口波特率
.CLK_FREQ (CLK_FREQ ) //时钟频率
)
uart_tx_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_data (po_data ), //input [7:0] pi_data
.pi_flag (po_flag ), //input pi_flag

.tx (tx ) //output tx
);

endmodule

tb_rs232.v

`timescale  1ns/1ns
module tb_rs232();

//wire define
wire tx ;

//reg define
reg sys_clk ;
reg sys_rst_n ;
reg rx ;

//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20;
sys_rst_n <= 1'b1;
end

//调用任务rx_byte
initial begin
#200
rx_byte();
end

//sys_clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
always #10 sys_clk = ~sys_clk;

//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task rx_byte(); //因为不需要外部传递参数,所以括号中没有输入
integer j;
for(j=0; j<8; j=j+1) //调用8次rx_bit任务,每次发送的值从0变化7
rx_bit(j);
endtask

//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task rx_bit(
input [7:0] data
);
integer i;
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20); //每发送1位数据延时5208个时钟周期
end
endtask

//------------------------ rs232_inst ------------------------
rs232 rs232_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.rx (rx ), //input rx

.tx (tx ) //output tx
);

endmodule


第32讲:使用SignalTap II嵌入式逻辑分析仪在线调试

看视频讲解:​​https://www.bilibili.com/video/BV17z411i7er​


第33讲:串口RS485

RS485是双向、半双工通信协议,信号采用差分传输方式,允许多个驱动器和接收器挂接在总线上,其中每个驱动器都能够脱离总线。

野火FPGA强化(1):串口_fpga开发_12


野火FPGA强化(1):串口_数据_13



实现目标

野火FPGA强化(1):串口_sed_14


野火FPGA强化(1):串口_fpga开发_15


系统框图

野火FPGA强化(1):串口_串口_16


控制板波形

野火FPGA强化(1):串口_串口_17


被控制板波形

野火FPGA强化(1):串口_sed_18


key_filter

`timescale  1ns/1ns

module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire key_in , //按键输入信号

output reg key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);

//reg define
reg [19:0] cnt_20ms ; //计数器

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0) //为低电平且已经达到最大值,不再进行清零
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;

endmodule

water_led

`timescale  1ns/1ns

module water_led
#(
parameter CNT_MAX = 25'd24_999_999
)
(
input wire sys_clk , //系统时钟50Mh
input wire sys_rst_n , //全局复位

output wire [3:0] led_out //输出控制led灯
);

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

//cnt:计数器计数1s
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'b0;
else if(cnt == CNT_MAX)
cnt <= 25'b0;
else
cnt <= cnt + 1'b1;

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

//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;

endmodule

breath_led

`timescale  1ns/1ns

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 ;
reg [9:0] cnt_1ms ;
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;

//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) ||
(cnt_1s_flag == 1'b0 && cnt_1ms > cnt_1s))
led_out <= 1'b0;
else
led_out <= 1'b1;

endmodule

led_ctrl

`timescale  1ns/1ns

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 )
led_out <= led_out_w;
else if(po_data[1] == 1'b1 )
//使四个led灯都显示呼吸灯状态
led_out <= {led_out_b,led_out_b,led_out_b,led_out_b};
else
led_out <= 4'b1111;

endmodule

uart_rx

`timescale  1ns/1ns

module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据

output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);

//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;

//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;

//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;

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

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
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计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;

endmodule

uart_tx

`timescale  1ns/1ns

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

rs485

`timescale  1ns/1ns

module 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 ; //呼吸灯

//--------------------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 ), //全局复位
.rx (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

tb_rs485

`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 ;

//对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 rs485_inst1.key_filter_w.CNT_MAX = 5 ;
defparam rs485_inst1.key_filter_b.CNT_MAX = 5 ;
defparam rs485_inst1.uart_rx_inst.UART_BPS = 1000000;
defparam rs485_inst1.uart_tx_inst.UART_BPS = 1000000;
defparam rs485_inst1.water_led_inst.CNT_MAX = 4000 ;
defparam rs485_inst1.breath_led_inst.CNT_1US_MAX = 4 ;
defparam rs485_inst1.breath_led_inst.CNT_1MS_MAX = 9 ;
defparam rs485_inst1.breath_led_inst.CNT_1S_MAX = 9 ;
//接收板参数
defparam rs485_inst2.key_filter_w.CNT_MAX = 5 ;
defparam rs485_inst2.key_filter_b.CNT_MAX = 5 ;
defparam rs485_inst2.uart_rx_inst.UART_BPS = 1000000;
defparam rs485_inst2.uart_tx_inst.UART_BPS = 1000000;
defparam rs485_inst2.water_led_inst.CNT_MAX = 4000 ;
defparam rs485_inst2.breath_led_inst.CNT_1US_MAX = 4 ;
defparam rs485_inst2.breath_led_inst.CNT_1MS_MAX = 99 ;
defparam rs485_inst2.breath_led_inst.CNT_1S_MAX = 99 ;

//发送板
//-------------rs485_inst1-------------
rs485 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灯
);

//接收板
//-------------rs485_inst2-------------
rs485 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