04_串口 RS485
1. 理论学习
1.1 单端传输
单端传输是指在传输过程中,在一根导线上传输对地之间的电平差,用这个电平差值来表示逻辑“0”和“1”(RS232 即为单端传输方式)。
1.2 差分传输
差分传输是使用两根线进行传输信号,这两根线上的信号振幅相等,相位相差 180 度,极性相反。在这两根线上传输的信号就是差分信号,信号接收端比较这两个信号电压的差值来判断发送端发送的逻辑“0”和逻辑“1”。
1.3 RS485 简介
RS485 通信协议和 RS232 是一样的
RS485 是 UART 的一种,在上一章节我们详细地介绍了 UART 串口通信以及 RS-232接口标准。RS-485 是针对 RS-232 接口的不足而出现的一种接口标准,本章节将为大家详细的介绍 RS-485 标准。RS-485 是双向、半双工通信协议,允许多个驱动器和接收器挂接在总线上,其中每个驱动器都能够脱离总线。所谓半双工就是指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。RS-485 采用差分传输方式.
1.4 RS485 收发器电路图
即当 RS485_RE 为高时发送过程,为低时接收过程。
1.5 双 RS485 通信连接图
2. 实验目标
在前面我们已经学习了流水灯和呼吸灯的控制方法,本章节就利用 RS-485 接口,使用两块 FPGA 开发板实现两块开发板之间的流水灯、呼吸灯控制。具体为:当按下其中一块开发板的按键 1 时,另一块开发板的流水灯亮,再次按下按键 1 时,流水灯灭;同理呼吸灯也是如此。需要注意的是我们开发板中的 led 只能显示一种状态(流水灯或呼吸灯),所以当显示流水灯/呼吸灯时,按下呼吸灯按键/流水灯按键后,led 会显示呼吸灯/流水灯。控制要求如图 31-3 所示。
3. 模块框图
3.1 顶层模块框图
3.2 按键消抖模块
3.3 呼吸灯模块
3.4 流水灯模块
3.5 串口接收模块
3.6 串口发送模块
这个模块与《RS232》章节中的 uart_tx 模块大致相同,唯一不同的是《RS232》章节中的 uart_tx 模块的 work_en(发送工作使能信号),我们需要将其作为输出连接到MAX3485 收发器中去控制信息的发送和接收。同时还要注意在《RS232》章节中的 uart_tx模块的 work_en 信号是拉高到数据位的最后一位,停止位并没有拉高。通过测试发现这会导致开发板在向另一块开发板发送信息时,rx 在没有接受到数据时会拉低几个时钟,而uart_rx 模块是通过 rx 的下降沿来判断是否传来数据的,这样就会导致开发板误以为另一块开发板发送过来了信息,导致结果出现错误。所以我们需要将 work_en(发送工作使能信号)拉高至停止位来解决这个问题。
波形图的不同
4. 波形图
4.1 led 灯控制模块
4.2 Uart_tx
5. RTL
5.1 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灯
);
//********************************************************************//
//****************** 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
5.2 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 ;
//************************************************************************//
//******************************* 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
5.3 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 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. Testbench
6.1 tb_rs485
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/03/12
// Module Name : tb_rs485
// Project Name : rs485
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : RS485仿真文件
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
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