6. 串口rs485

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

本章节将为大家介绍如何利用两块FPGA开发板,实现板与板之间的通信。

6.1. 理论学习

6.1.1. RS485简介

RS485是UART的一种,在上一章节我们详细地介绍了UART串口通信以及RS-232接口标准。RS-485是针对RS-232接口的不足而出现的一种接口标准,本章节将为大家详细的介绍RS-485标准。

RS-485是双向、半双工通信协议,允许多个驱动器和接收器挂接在总线上,其中每个驱动器都能够脱离总线。所谓半双工就是指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。RS-485采用差分传输方式,什么是差分传输方式?这里先为大家讲解一下单端传输方式。单端传输是指在传输过程中,在一根导线上传 输对地之间的电平差,用这个电平差值来表示逻辑“0”和“1”(RS232即为单端传输方式)。而差分传输是使用两根线进行传输信号,这两根线上的信号振幅相等,相位相差180度,极性相反。在这两根线上传输的信号就是差分信号,信号接收端比较这两个信号电压的差值来判断发送端发送的逻辑“0”和逻辑“1”。单端传输 和差分传输如图 35‑1所示。

RS485002

图 35‑1 单端及差分传输图

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

RS485003

图 35‑2 差分传输抑制共模干扰图

RS485针对RS232发展而来的,它们的通信协议是相同的。所以RS-485接口发送和接收数据的通信协议与上一章所讲的RS232的通信协议是一样的,大家可参考上一章节进行学习。

6.2. 实战演练

6.2.1. 实验目标

在前面我们已经学习了流水灯和呼吸灯的控制方法,本章节就利用RS-485接口,使用两块FPGA开发板实现两块开发板之间的流水灯、呼吸灯控制。具体为:当按下其中一块开发板的按键1时,另一块开发板的流水灯亮,再次按下按键1时,流水灯灭;同理呼吸灯也是如此。需要注意的是我们开发板中的led只能显示一种状态( 流水灯或呼吸灯),所以当显示流水灯/呼吸灯时,按下呼吸灯按键/流水灯按键后,led会显示呼吸灯/流水灯。控制要求如图 35‑3所示。

RS485004

图 35‑3 led灯显示控制图

6.2.2. 硬件资源

本次实验我们需使用到开发板上的按键、LED灯以及RS485收发器芯片,其中按键及LED灯资源在前面很多章节都有所讲解,在此就不再过多叙述了。RS485收发器电路如图 35‑4所示。

RS485005

图 35‑4 RS485收发器电路图

如图 35‑4所示,MAX3485为RS485收发器芯片,该收发器可实现差分信号与TTL信号之间的转换。RS485是半双工通信,当引脚2为低电平时,接收使能;当引脚3为高电平时,发送使能。这里我们将引脚2和引脚3连接在一起,即当RS485_RE为高时发送过程,为低时接收过程。

因为我们是通过两块板进行RS485通信实验,其之间的连线如图 35‑5所示。

RS485006

图 35‑5 双RS485通信连接图

由上图可以看到我们是485B口连接另一块板子的485B口,485A口连接到另一块板子的485A口。

RS485007

图 35‑6 跳帽选择图

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

6.3. 程序设计

6.3.1. 整体说明

根据实验目标可知,要实现这个功能,首先流水灯和呼吸灯的模块是必不可少的;其次是串口发送和接收模块;而如何去控制流水灯和呼吸灯呢?这里我们只需加一个led控制模块即可。该工程的整体框图如图 35‑7所示。

RS485008

图 35‑7 RS485通信实验整体框图

通过上图可以看到,该工程共分7个模块。各模块简介见表格 35‑1。

表格 35‑1 rs485工程模块简介

模块名称

功能描述

key_fifter

按键消抖模块

breath_led

呼吸灯模块

water_led

呼吸灯模块

led_ctrl

led灯控制模块

uart_rx

串口接收模块

uart_tx

串口发送模块

rs485

rs485顶层模块

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

6.3.1.1. 按键消抖模块

在前面按键消抖章节已经对按键消抖模块进行了详细的讲解,在此就不再进行说明,直接调用这个模块即可。

6.3.1.2. 呼吸灯模块

在《呼吸灯》章节已经对呼吸灯产生方法进行了详细讲解,在此不再过多叙述,直接调用这个模块即可。

6.3.1.3. 流水灯模块

在《流水灯》章节已经对流水灯产生方法进行了详细讲解,在此不再过多叙述,直接调用这个模块即可。

6.3.1.4. 串口接收模块

RS485的通信协议与RS232的通信协议一致,所以该模块直接调用《RS232》章节中的uart_rx模块即可。

6.3.1.5. 串口发送模块

这个模块与《RS232》章节中的uart_tx模块大致相同,唯一不同的是《RS232》章节中的uart_tx模块的work_en(发送工作使能信号),我们需要将其作为输出连接到MAX3485收发器中去控制信息的发送和接收。同时还要注意在《RS232》章节中的uart_tx模块的work_en信号是拉 高到数据位的最后一位,停止位并没有拉高。通过测试发现这会导致开发板在向另一块开发板发送信息时,rx在没有接受到数据时会拉低几个时钟,而uart_rx模块是通过rx的下降沿来判断是否传来数据的,这样就会导致开发板误以为另一块开发板发送过来了信息,导致结果出现错误。所以我们需要将work_en(发送工作 使能信号)拉高至停止位来解决这个问题,代码如代码清单 35‑1所示。

代码清单 35‑1 RS485串口发送模块参考代码(uart_tx.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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 ;

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

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

如上代码12行所示需将work_en信号输出,同时只需改动50行和68行的代码即可使使能信号拉高至停止位。

6.3.1.6. led灯控制模块

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

模块框图

RS485009

图 35‑8 led控制模块框图

模块各个信号的简介,如表格 35‑2所示。

表格 35‑2 led控制模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

时钟信号,50MHz

sys_rst_n

1bit

input

复位信号,低有效

water_key_flag

1bit

input

流水灯按键有效信号

breath_key_flag

1bit

input

呼吸灯按键有效信号

led_out_w

4bit

input

流水灯

led_out_b

1bit

input

呼吸灯

po_data

8bit

input

接收数据

pi_flag

1bit

output

发送标志信号

pi_data

8bit

output

发送数据

led_out

4bit

output

输出led灯显示

下面通过波形图去了解该模块的具体时序。

波形图绘制

RS485010

图 35‑9 led控制模块发送信号波形图

根据实验任务我们知道是用按键去控制另外一块开发的led灯显示的,所以很明显这个按键就是控制信号,而我们要传输的也是按键控制信号。而这个按键控制信号要满足什么条件才能对led灯正确控制呢?在流水灯和呼吸灯章节我们是让灯光一直维持流水灯或呼吸灯的状态。而这次是用按键信号去控制led灯的,就需要用按键信号 去让led灯维持呼吸灯或流水灯的状态,很明显如果我们只用一个时钟的按键消抖后的标志信号去控制的话,很难达到我们的led灯的显示要求,所以我们就需要用按键信号去产生控制led灯状态的控制信号,这个信号在改变led灯状态时需维持有效信号不变。因为有两种led灯状态,所以我们需产生两个状态标志信号。

water_led_flag(breath_led_flag):流水灯状态标志信号(呼吸灯状态标志信号),高有效。当我们的流水灯按键信号有效时(water_key_flag=1),我们拉高流水灯状态标志信号。当呼吸灯按键信号有效时,说明此时需要使led灯显示呼吸灯状态,这时我们就需要将流水灯状态标志 信号拉低,同时将呼吸灯状态标志信号拉高,使led灯显示呼吸灯状态。当再次按下呼吸灯按键时,拉低呼吸灯状态标志信号,使led灯从显示呼吸灯变为不显示,同理当led灯显示流水灯时按下流水灯按键时也是如此。

通过对《RS232》章节发送模块的学习,我们知道信息是通过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 完成。

这样我们就能将控制信息发送出去了,下面我们来看看是如何接收信息来控制led灯状态的。

RS485011

图 35‑10 led控制模块接收信号波形图

通过对《RS232》章节的学习我们知道,接收的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即可。

根据传过来的信号我们就可实现对led灯的控制了,如此我们就可以实现板对板的信息传输及控制了。

代码编写

参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 35‑2。

代码清单 35‑2 led控制模块参考代码(led_ctrl.v)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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灯

 );

 ////
 //\* Parameter and Internal Signal \//
 ////

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

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

 //按下呼吸灯按键或流水灯按键时,开始发送数据
 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

6.3.1.7. 顶层模块

模块框图

RS485012

图 35‑11 rs485顶层模块框图

模块各个信号的简介,如表格 35‑3所示。

表格 35‑3 rs485顶层模块输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1bit

input

系统时钟,50MHz

sys_rst_n

1bit

input

复位信号

key

2bit

input

按键信号

rx

1bit

input

串口接收信号

work_en

1bit

output

电平转换使能信号

tx

1bit

output

串口发送信号

led

4bit

output

输出led灯

代码编写

rs485顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接,代码编写较为容易,无需波形图的绘制。顶层参考代码,具体见代码清单 35‑3。

代码清单 35‑3 rs485顶层模块参考代码(rs485.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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 and Internal Signal \//
////

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

因为我们需要用到两个按键信号,所以我们需要例化两次按键消抖模块,产生两个按键有效信号。

6.3.1.8. RTL视图

实验工程通过仿真验证后,使用Quartus软件对实验工程进行编译,编译完成后,我们查看一下RTL视图,仔细看RTL视图展示信息与顶层模块框图是一致,具体见图 35‑12所示。

RS485013

图 35‑12 实验工程RTL视图

6.3.1.9. 仿真验证

仿真代码编写

顶层模块参考代码介绍完毕,开始对顶层模块进行仿真,对顶层模块的仿真就是对实验工程的整体仿真。顶层模块仿真参考代码,具体见代码清单 35‑4。

代码清单 35‑4 rs485顶层模块仿真参考代码(tb_rs485.v)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
module tb_rs485();

////
//\* Parameter and Internal Signal \//
////

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


 ////
 //\* Instantiation \//
 ////

 //发送板
 //-------------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

仿真波形分析

使用ModelSim软件对代码进行仿真,仿真波形如下所示。

RS485014

图 35‑13 rs485仿真波形图(一)

如图 35‑13所示为发送板的仿真波形图,从波形图中可以看到发送的信息与我们仿真模拟的的按键信号是一致的,其余信号的时序也与我们的设计结果是相同的。

RS485015

图 35‑14 rs485仿真波形图(二)

如图 35‑14所示为接收板的仿真波形图,从波形图可以看到接收板接收的信息与我们发送的信息是一致的。下面我们看看led灯是不是按照发送板的按键进行显示的。

RS485016

图 35‑15 rs485仿真波形图(四)

RS485017

图 35‑16 rs485仿真波形图(五)

由图 35‑14、图 35‑15、图 35‑16仿真波形图中的led_out信号可以看到,led灯是按照我们模拟输入的按键信息进行显示,这说明我们的工程可以达到我们的实验要求。

6.3.1.10. SignalTap波形抓取

这里我们利用quartus软件自带的SignalTap工具对波形进行实时抓取来验证我们的设计是否正确,如下图所示。

RS485018

图 35‑17 SignalTap波形抓取图(一)

RS485019

图 35‑18 SignalTap波形抓取图(二)

如图 35‑17、图 35‑18抓取的是按下流水灯按键的发送波形图以及再次按下流水灯按键的发送波形图,从图中可以看到发送的数据pi_data[0]由0变为1再变为0,符合我们的发送要求。

RS485020

图 35‑19 SignalTap波形抓取图(三)

RS485021

图 35‑20 SignalTap波形抓取图(四)

如图 35‑19、图 35‑20抓取的是按下呼吸灯按键的发送波形图以及再次按下呼吸灯按键的发送波形图,从图中可以看到发送的数据pi_data[1]由0变为1再变为0,符合我们的发送要求。

RS485022

图 35‑21 SignalTap波形抓取图(五)

如图 35‑21所示,抓取的是流水灯时按下呼吸灯时的发送波形图,可以看到我们发送的数据pi_data[0]由1变为了0,而pi_data[1]由0变为了1,与我们的设计是一致的。由以上五个抓取的发送波形图可以看到发送时能达到我们的实验要求的,接下来看看接收的波形图。

RS485023

图 35‑22 SignalTap波形抓取图(六)

RS485024

图 35‑23 SignalTap波形抓取图(七)

如图 35‑22、图 35‑23所示,接收板的状态是流水灯(po_data[0]=1)时,发送板按下流水灯按键,从图 35‑22中可以看到pi_data[1]由1变为0了,流水灯不亮;当发送板按下呼吸灯按键时,从图 35‑23中可以看到po_data[1]由1变为0,po_data[2]由0变为1,说明此时接收板的状态由流水灯变为呼吸灯,和我们的设计是一样的。

RS485025

图 35‑24 SignalTap波形抓取图(八)

如图 35‑24所示,当接收板的状态为呼吸灯时,发送板按下流水灯按键,此时抓取接收板的波形图可发现接收板接收的数据po_data[1]由1变为0,po_data[0]由0变为1;呼吸灯变为流水灯,与我们的设计一致。

由上抓取的发送和接收的波形图可知,我们的设计是能满足我们的实验要求的。

6.4. 上板调试

6.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 35‑4所示。

表格 35‑4 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

input

E1

时钟

sys_rst_n

input

M15

复位

key[0]

input

M2

按键

key[1]

input

M1

按键

rx

input

K8

串口接收数据

work_en

output

C11

发送使能

tx

output

M7

串口发送数据

led[0]

output

L7

led灯

led[1]

output

M6

led灯

led[2]

output

P3

led灯

led[3]

output

N3

led灯

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 35‑25所示。

RS485026

图 35‑25 管脚分配

6.4.1.1. 结果验证

管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将两块开发板的电源与下载线与开发板连接好,如图 35‑26所示。

RS485027

图 35‑26 程序下载连接图

连接板卡各自短路帽,使用杜邦线连接两板卡,开发板485A口连接另一块开发板的485A口,开发板485B口连接另一块开发板的485B口,如图 35‑27所示。

RS485028

图 35‑27 RS485通信连接图

打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下载,随后界面会显示下载成功,如图 35‑28所示。

RS485029

图 35‑28 下载成功界面

下载成功后即可以开始验证了。按照实验目标描述进行操作,若显示结果与实验目标描述相同,则说明验证成功。

6.5. 章末总结

到这里,本章节就讲解完毕了。通过对本章节的学习相信大家对RS485与RS232有了更详细了解,同时对它们之间的区别也有了更加清晰的认知。