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).
insert image description here

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.
insert image description here

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

insert image description here
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

insert image description here

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.
insert image description here

3. Block Diagram

3.1 Top-level module block diagram

insert image description here
insert image description here
insert image description here

3.2 Key debounce module

insert image description here

3.3 Breathing light module

insert image description here

3.4 Water light module

insert image description here

3.5 Serial receiving module

insert image description here

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.
insert image description here
Waveform Diagram Differences
insert image description here

4. Waveform diagram

4.1 led light control module

insert image description here
insert image description here

4.2 Uart_tx

insert image description here

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

Guess you like

Origin blog.csdn.net/HeElLose/article/details/131424491