04_Serial port RS485
1. Theoretical study
1.1 Single-ended transmission
Single-ended transmission means that during the transmission process, the level difference between the ground and the ground is transmitted on a wire, and this level difference is used to represent logic "0" and "1" (RS232 is a single-ended transmission method).
1.2 Differential transmission
Differential transmission uses two lines to transmit signals. The signals on the two lines have equal amplitudes, 180-degree phase differences, and opposite polarities. The signals transmitted on these two lines are differential signals, and the signal receiving end compares the difference between the two signal voltages to judge the logic "0" and logic "1" sent by the sending end.
1.3 Introduction to RS485
RS485 communication protocol is the same as RS232.
RS485 is a kind of UART. In the previous chapter, we introduced UART serial communication and RS-232 interface standard in detail. RS-485 is an interface standard for the shortage of RS-232 interface. This chapter will introduce the RS-485 standard in detail. RS-485 is a bi-directional, half-duplex communication protocol that allows multiple drivers and receivers to be attached to the bus, where each driver can be detached from the bus. The so-called half-duplex means that data can be transmitted in two directions of a signal carrier, but cannot be transmitted at the same time. RS-485 uses differential transmission.
1.4 RS485 Transceiver Circuit Diagram
That is, when RS485_RE is high, it is in the process of sending, and when it is low, it is in the process of receiving.
1.5 Dual RS485 communication connection diagram
2. Experimental objectives
We have learned the control method of the running water lamp and the breathing lamp in the previous section. In this chapter, we use the RS-485 interface to use two FPGA development boards to realize the control of the running water lamp and the breathing lamp between the two development boards. Specifically: when button 1 of one of the development boards is pressed, the running water light of the other development board is on, and when button 1 is pressed again, the running water light is off; the same is true for the breathing light. It should be noted that the LED in our development board can only display one state (flowing light or breathing light), so when the flowing light/breathing light is displayed, after pressing the breathing light button/flowing light button, the led will display the breathing light/flowing light. Control requirements are shown in Figure 31-3.
3. Block Diagram
3.1 Top-level module block diagram
3.2 Key debounce module
3.3 Breathing light module
3.4 Water light module
3.5 Serial receiving module
3.6 Serial sending module
This module is roughly the same as the uart_tx module in the "RS232" chapter, the only difference is the work_en (send work enable signal) of the uart_tx module in the "RS232" chapter, we need to connect it as an output to the MAX3485 transceiver to control the sending and receiving of information. At the same time, it should also be noted that the work_en signal of the uart_tx module in the "RS232" chapter is pulled high to the last bit of the data bit, and the stop bit is not pulled high. Through testing, it is found that when the development board sends information to another development board, rx will pull down several clocks when no data is received, and the uart_rx module judges whether data is transmitted through the falling edge of rx, which will cause the development board to mistakenly think that another development board has sent information, resulting in an error in the result. So we need to pull work_en (send work enable signal) high to the stop bit to solve this problem.
Waveform Diagram Differences
4. Waveform diagram
4.1 led light control module
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